Compare commits
10 Commits
0ba7af6489
...
master
30
MindShift-Windows/BUILD_APK.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# 📱 How to Build the Offline APK
|
||||||
|
|
||||||
|
I have converted the application to **Offline Mode**. It now stores all data locally on the device, so you **DO NOT** need to deploy a backend server.
|
||||||
|
|
||||||
|
## 🚀 Steps to Build APK
|
||||||
|
|
||||||
|
1. **Copy the Project:**
|
||||||
|
Download the entire `MindShift-Windows` folder to your local machine.
|
||||||
|
|
||||||
|
2. **Install Dependencies:**
|
||||||
|
Open a terminal in the `MindShift-Windows` folder and run:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Open Android Studio:**
|
||||||
|
Run the following command to open the project in Android Studio (you must have Android Studio installed):
|
||||||
|
```bash
|
||||||
|
npx cap open android
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Build & Run:**
|
||||||
|
- In Android Studio, wait for Gradle to sync.
|
||||||
|
- Click the **Run** (Play) button to test on an emulator/device.
|
||||||
|
- To build the APK: Go to `Build > Build Bundle(s) / APK(s) > Build APK(s)`.
|
||||||
|
|
||||||
|
## ℹ️ Notes
|
||||||
|
- The app now uses `localStorage` to save your Moods, Thoughts, and Gratitude entries.
|
||||||
|
- If you uninstall the app, your data will be cleared (standard behavior for local-only apps).
|
||||||
|
- No internet connection is required for the app to function.
|
||||||
55
MindShift-Windows/BUILD_NOW.bat
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
@echo off
|
||||||
|
echo ===================================================
|
||||||
|
echo MindShift APK Builder (One-Click)
|
||||||
|
echo ===================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Check for Java
|
||||||
|
where java >nul 2>nul
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [ERROR] Java is not installed or not in PATH.
|
||||||
|
echo Please install Java (JDK 17 recommended) and try again.
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
:: Check for Android Home
|
||||||
|
if "%ANDROID_HOME%"=="" (
|
||||||
|
echo [ERROR] ANDROID_HOME environment variable is not set.
|
||||||
|
echo Please install Android Studio and set ANDROID_HOME to your SDK location.
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [1/3] Installing dependencies...
|
||||||
|
call npm install
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo Failed to install dependencies.
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [2/3] Syncing Capacitor...
|
||||||
|
call npx cap sync
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo Failed to sync Capacitor.
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [3/3] Building APK (Debug Mode)...
|
||||||
|
cd android
|
||||||
|
call gradlew.bat assembleDebug
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ===================================================
|
||||||
|
if exist "android\app\build\outputs\apk\debug\app-debug.apk" (
|
||||||
|
echo [SUCCESS] APK created successfully!
|
||||||
|
echo Location: android\app\build\outputs\apk\debug\app-debug.apk
|
||||||
|
explorer "android\app\build\outputs\apk\debug\"
|
||||||
|
) else (
|
||||||
|
echo [ERROR] Build failed. Please check the logs above.
|
||||||
|
)
|
||||||
|
echo ===================================================
|
||||||
|
pause
|
||||||
BIN
MindShift-Windows/MindShift-App.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.1.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.2.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.3.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.4.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.5.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.6.apk
Normal file
BIN
MindShift-Windows/MindShift-v1.0.7.apk
Normal file
101
MindShift-Windows/android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||||
|
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.aar
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
#*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Android Profiling
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# Cordova plugins for Capacitor
|
||||||
|
capacitor-cordova-android-plugins
|
||||||
|
|
||||||
|
# Copied web assets
|
||||||
|
app/src/main/assets/public
|
||||||
|
|
||||||
|
# Generated Config files
|
||||||
|
app/src/main/assets/capacitor.config.json
|
||||||
|
app/src/main/assets/capacitor.plugins.json
|
||||||
|
app/src/main/res/xml/config.xml
|
||||||
2
MindShift-Windows/android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/build/*
|
||||||
|
!/build/.npmkeep
|
||||||
54
MindShift-Windows/android/app/build.gradle
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace "com.mindshift.app"
|
||||||
|
compileSdk rootProject.ext.compileSdkVersion
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.mindshift.app"
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
aaptOptions {
|
||||||
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||||
|
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
flatDir{
|
||||||
|
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
|
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||||
|
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||||
|
implementation project(':capacitor-android')
|
||||||
|
testImplementation "junit:junit:$junitVersion"
|
||||||
|
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||||
|
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||||
|
implementation project(':capacitor-cordova-android-plugins')
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: 'capacitor.build.gradle'
|
||||||
|
|
||||||
|
try {
|
||||||
|
def servicesJSON = file('google-services.json')
|
||||||
|
if (servicesJSON.text) {
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||||
|
}
|
||||||
19
MindShift-Windows/android/app/capacitor.build.gradle
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_21
|
||||||
|
targetCompatibility JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||||
|
dependencies {
|
||||||
|
implementation project(':capacitor-haptics')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasProperty('postBuildExtras')) {
|
||||||
|
postBuildExtras()
|
||||||
|
}
|
||||||
21
MindShift-Windows/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.getcapacitor.myapp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void useAppContext() throws Exception {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
||||||
41
MindShift-Windows/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/title_activity_main"
|
||||||
|
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths"></meta-data>
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<!-- Permissions -->
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.mindshift.app;
|
||||||
|
|
||||||
|
import com.getcapacitor.BridgeActivity;
|
||||||
|
|
||||||
|
public class MainActivity extends BridgeActivity {}
|
||||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#26A69A"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
</vector>
|
||||||
BIN
MindShift-Windows/android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">MindShift</string>
|
||||||
|
<string name="title_activity_main">MindShift</string>
|
||||||
|
<string name="package_name">com.mindshift.app</string>
|
||||||
|
<string name="custom_url_scheme">com.mindshift.app</string>
|
||||||
|
</resources>
|
||||||
22
MindShift-Windows/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:background">@null</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||||
|
<item name="android:background">@drawable/splash</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-path name="my_images" path="." />
|
||||||
|
<cache-path name="my_cache_images" path="." />
|
||||||
|
</paths>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.getcapacitor.myapp;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() throws Exception {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
MindShift-Windows/android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:8.7.2'
|
||||||
|
classpath 'com.google.gms:google-services:4.4.2'
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "variables.gradle"
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
6
MindShift-Windows/android/capacitor.settings.gradle
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||||
|
include ':capacitor-android'
|
||||||
|
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||||
|
|
||||||
|
include ':capacitor-haptics'
|
||||||
|
project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android')
|
||||||
22
MindShift-Windows/android/gradle.properties
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app's APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
BIN
MindShift-Windows/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
MindShift-Windows/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
252
MindShift-Windows/android/gradlew
vendored
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
94
MindShift-Windows/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
5
MindShift-Windows/android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
include ':app'
|
||||||
|
include ':capacitor-cordova-android-plugins'
|
||||||
|
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||||
|
|
||||||
|
apply from: 'capacitor.settings.gradle'
|
||||||
16
MindShift-Windows/android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
ext {
|
||||||
|
minSdkVersion = 23
|
||||||
|
compileSdkVersion = 35
|
||||||
|
targetSdkVersion = 35
|
||||||
|
androidxActivityVersion = '1.9.2'
|
||||||
|
androidxAppCompatVersion = '1.7.0'
|
||||||
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
|
androidxCoreVersion = '1.15.0'
|
||||||
|
androidxFragmentVersion = '1.8.4'
|
||||||
|
coreSplashScreenVersion = '1.0.1'
|
||||||
|
androidxWebkitVersion = '1.12.1'
|
||||||
|
junitVersion = '4.13.2'
|
||||||
|
androidxJunitVersion = '1.2.1'
|
||||||
|
androidxEspressoCoreVersion = '3.6.1'
|
||||||
|
cordovaAndroidVersion = '10.1.1'
|
||||||
|
}
|
||||||
62
MindShift-Windows/build_apk.sh
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Starting Automated APK Build in WSL..."
|
||||||
|
|
||||||
|
# --- SETUP JAVA 21 (Portable) ---
|
||||||
|
JAVA_DIR="$HOME/java-21"
|
||||||
|
if [ ! -d "$JAVA_DIR" ]; then
|
||||||
|
echo "⬇️ Downloading OpenJDK 21 (Temurin)..."
|
||||||
|
mkdir -p "$JAVA_DIR"
|
||||||
|
cd "$JAVA_DIR"
|
||||||
|
# Adoptium Temurin JDK 21
|
||||||
|
wget -q -L https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.2%2B13/OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz -O jdk21.tar.gz
|
||||||
|
tar -xzf jdk21.tar.gz --strip-components=1
|
||||||
|
rm jdk21.tar.gz
|
||||||
|
echo "✅ Java 21 Installed."
|
||||||
|
else
|
||||||
|
echo "✅ Java 21 already present."
|
||||||
|
fi
|
||||||
|
|
||||||
|
export JAVA_HOME="$JAVA_DIR"
|
||||||
|
export PATH="$JAVA_HOME/bin:$PATH"
|
||||||
|
|
||||||
|
# --- SETUP ANDROID SDK ---
|
||||||
|
SDK_DIR="$HOME/android-sdk"
|
||||||
|
mkdir -p "$SDK_DIR/cmdline-tools"
|
||||||
|
|
||||||
|
if [ ! -d "$SDK_DIR/cmdline-tools/latest" ]; then
|
||||||
|
echo "⬇️ Downloading Android Command Line Tools..."
|
||||||
|
cd "$SDK_DIR/cmdline-tools"
|
||||||
|
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O tools.zip
|
||||||
|
unzip -q tools.zip
|
||||||
|
mv cmdline-tools latest
|
||||||
|
rm tools.zip
|
||||||
|
echo "✅ Tools downloaded."
|
||||||
|
else
|
||||||
|
echo "✅ Android Tools already present."
|
||||||
|
fi
|
||||||
|
|
||||||
|
export ANDROID_HOME="$SDK_DIR"
|
||||||
|
export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools"
|
||||||
|
|
||||||
|
# --- INSTALL SDK PACKAGES ---
|
||||||
|
echo "📦 Checking SDK Packages..."
|
||||||
|
yes | sdkmanager --licenses > /dev/null
|
||||||
|
# Only install if missing to save time
|
||||||
|
if [ ! -d "$SDK_DIR/platforms/android-35" ]; then
|
||||||
|
echo "⬇️ Installing Platform 35..."
|
||||||
|
sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- BUILD APK ---
|
||||||
|
PROJECT_DIR="/mnt/e/TRAE Playground/MindShift-Windows/android"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
echo "🔨 Building APK with Java 21..."
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# --- SUCCESS ---
|
||||||
|
echo "🎉 Build Complete!"
|
||||||
|
echo "APK Location: $PROJECT_DIR/app/build/outputs/apk/debug/app-debug.apk"
|
||||||
9
MindShift-Windows/capacitor.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { CapacitorConfig } from '@capacitor/cli';
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: 'com.mindshift.app',
|
||||||
|
appName: 'MindShift',
|
||||||
|
webDir: 'src'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
695
MindShift-Windows/package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mindshift-cbt-therapy",
|
"name": "mindshift-cbt-therapy",
|
||||||
"version": "1.0.0",
|
"version": "1.0.7",
|
||||||
"description": "MindShift - Your personal CBT therapy companion for Windows 11",
|
"description": "MindShift - Your personal CBT therapy companion for Windows 11",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"homepage": "./",
|
"homepage": "./",
|
||||||
@@ -61,6 +61,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@capacitor/android": "^7.4.4",
|
||||||
|
"@capacitor/cli": "^7.4.4",
|
||||||
|
"@capacitor/core": "^7.4.4",
|
||||||
|
"@capacitor/haptics": "^7.0.2",
|
||||||
"electron-updater": "^6.1.7"
|
"electron-updater": "^6.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -135,11 +135,21 @@ export const gratitudeAPI = {
|
|||||||
// Progress API
|
// Progress API
|
||||||
export const progressAPI = {
|
export const progressAPI = {
|
||||||
async getProgressStats() {
|
async getProgressStats() {
|
||||||
return await apiCall('/progress/stats');
|
return await apiCall('/dashboard/stats');
|
||||||
},
|
},
|
||||||
|
|
||||||
async getProgressHistory() {
|
async getProgressHistory(days = 30) {
|
||||||
return await apiCall('/progress/history');
|
return await apiCall(`/progress?days=${days}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Exercise API
|
||||||
|
export const exerciseAPI = {
|
||||||
|
async logSession(type, duration) {
|
||||||
|
return await apiCall('/exercises', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ exerciseType: type, duration })
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
257
MindShift-Windows/src/guided-styles.css
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/* Guided Relaxation - Mindfulness Session */
|
||||||
|
.guided-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: radial-gradient(circle at center, #2E7D32, #004D40);
|
||||||
|
z-index: 6000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode specific backgrounds */
|
||||||
|
.guided-overlay.mode-body_scan {
|
||||||
|
background: radial-gradient(circle at center, #4A148C, #311B92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-overlay.mode-visualization {
|
||||||
|
background: radial-gradient(circle at center, #00695C, #004D40);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-title-large {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
animation: slideInDown 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-step-icon {
|
||||||
|
font-size: 100px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
animation: floatIcon 4s ease-in-out infinite;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(255,255,255,0.4));
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatIcon {
|
||||||
|
0%, 100% { transform: translateY(0) scale(1); }
|
||||||
|
50% { transform: translateY(-20px) scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-instruction {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 800px;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-sub {
|
||||||
|
font-size: 20px;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
max-width: 600px;
|
||||||
|
font-weight: 300;
|
||||||
|
animation: fadeIn 0.5s ease 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar for auto-playing sessions */
|
||||||
|
.guided-progress-bar-container {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 300px;
|
||||||
|
height: 6px;
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-progress-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: white;
|
||||||
|
width: 0%;
|
||||||
|
transition: width linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode Selection Menu */
|
||||||
|
.guided-mode-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-mode-card {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-mode-card:hover {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-mode-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-mode-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-bottom-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-pause-btn {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: white;
|
||||||
|
color: var(--primary-dark);
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 32px;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-pause-btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-pause-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pulse Animation for Body Scan */
|
||||||
|
.body-scan-pulse {
|
||||||
|
position: absolute;
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid rgba(255,255,255,0.3);
|
||||||
|
animation: scanPulse 4s infinite;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scanPulse {
|
||||||
|
0% { transform: scale(0.8); opacity: 0; }
|
||||||
|
50% { opacity: 1; }
|
||||||
|
100% { transform: scale(1.5); opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RTL Fixes for Guided */
|
||||||
|
html[dir="rtl"] .guided-controls {
|
||||||
|
right: auto;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stress Slider */
|
||||||
|
.stress-slider-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stress-slider {
|
||||||
|
width: 100%;
|
||||||
|
height: 10px;
|
||||||
|
background: rgba(255,255,255,0.3);
|
||||||
|
border-radius: 5px;
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stress-slider::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stress-value {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Settings Modal */
|
||||||
|
.guided-settings-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: none;
|
||||||
|
animation: slideInUp 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-settings-panel.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Summary Card */
|
||||||
|
.summary-card {
|
||||||
|
background: white;
|
||||||
|
color: var(--on-surface);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 24px;
|
||||||
|
text-align: center;
|
||||||
|
animation: zoomIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-stat {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--primary);
|
||||||
|
margin: 20px 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||||
<title>MindShift - CBT Therapy App</title>
|
<title>MindShift - CBT Therapy App</title>
|
||||||
|
|
||||||
<!-- PWA Meta Tags -->
|
<!-- PWA Meta Tags -->
|
||||||
<meta name="theme-color" content="#FF6B6B">
|
<meta name="theme-color" content="#8ECAE6">
|
||||||
<meta name="description" content="Your personal CBT therapy companion for mood management and mental wellness">
|
<meta name="description" content="Your personal CBT therapy companion for mood management and mental wellness">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
<!-- Styles -->
|
<!-- Styles -->
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<link rel="stylesheet" href="guided-styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Animated Background Orbs -->
|
<!-- Animated Background Orbs -->
|
||||||
@@ -54,6 +55,9 @@
|
|||||||
MindShift
|
MindShift
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<button class="nav-item" onclick="showLanguageModal()" style="color: var(--primary);">
|
||||||
|
<span class="material-icons">language</span>
|
||||||
|
</button>
|
||||||
<div style="position: relative;">
|
<div style="position: relative;">
|
||||||
<button class="nav-item" onclick="toggleNotifications()" style="color: white;">
|
<button class="nav-item" onclick="toggleNotifications()" style="color: white;">
|
||||||
<span class="material-icons">notifications</span>
|
<span class="material-icons">notifications</span>
|
||||||
@@ -72,7 +76,11 @@
|
|||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main id="main-content" class="main-content">
|
<main id="main-content" class="main-content">
|
||||||
<!-- Content will be dynamically loaded here -->
|
<!-- Loading State -->
|
||||||
|
<div id="initial-loader" style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 80vh; color: #666;">
|
||||||
|
<div class="spinner" style="width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid #FF6B6B; border-radius: 50%; animation: spin 1s linear infinite;"></div>
|
||||||
|
<p style="margin-top: 20px; font-family: sans-serif;">Initializing MindShift...</p>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Bottom Navigation -->
|
<!-- Bottom Navigation -->
|
||||||
@@ -104,8 +112,19 @@
|
|||||||
<span class="material-icons">add</span>
|
<span class="material-icons">add</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||||
|
</style>
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script type="module" src="api.js"></script>
|
<script>
|
||||||
<script type="module" src="app.js"></script>
|
window.addEventListener('error', function(e) {
|
||||||
|
// Display error on screen if app fails to load
|
||||||
|
var loader = document.getElementById('initial-loader');
|
||||||
|
if (loader) {
|
||||||
|
loader.innerHTML = '<div style="color: red; padding: 20px; text-align: center;"><h3>Error Loading App</h3><p>' + e.message + '</p></div>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<script type="module" src="./app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
228
MindShift-Windows/src/offline-api.js
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
// Offline API Implementation using LocalStorage
|
||||||
|
// This replaces the server-based API for the offline APK build
|
||||||
|
|
||||||
|
// --- Local Database Helper ---
|
||||||
|
const db = {
|
||||||
|
get(key) {
|
||||||
|
const data = localStorage.getItem(`mindshift_${key}`);
|
||||||
|
return data ? JSON.parse(data) : [];
|
||||||
|
},
|
||||||
|
set(key, data) {
|
||||||
|
localStorage.setItem(`mindshift_${key}`, JSON.stringify(data));
|
||||||
|
},
|
||||||
|
add(key, item) {
|
||||||
|
const data = this.get(key);
|
||||||
|
item.id = Date.now().toString();
|
||||||
|
item.created_at = new Date().toISOString();
|
||||||
|
// Add user_id if logged in
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (user) item.user_id = user.id;
|
||||||
|
|
||||||
|
data.push(item);
|
||||||
|
this.set(key, data);
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
update(key, id, updates) {
|
||||||
|
const data = this.get(key);
|
||||||
|
const index = data.findIndex(item => item.id === id);
|
||||||
|
if (index !== -1) {
|
||||||
|
data[index] = { ...data[index], ...updates };
|
||||||
|
this.set(key, data);
|
||||||
|
return data[index];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
remove(key, id) {
|
||||||
|
const data = this.get(key);
|
||||||
|
const newData = data.filter(item => item.id !== id);
|
||||||
|
this.set(key, newData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCurrentUser() {
|
||||||
|
const userStr = localStorage.getItem('mindshift_currentUser');
|
||||||
|
return userStr ? JSON.parse(userStr) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Authentication API ---
|
||||||
|
export const authAPI = {
|
||||||
|
async register(name, email, password) {
|
||||||
|
// Simulate network delay
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
|
||||||
|
const users = db.get('users');
|
||||||
|
if (users.find(u => u.email === email)) {
|
||||||
|
throw new Error('Email already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUser = { id: Date.now().toString(), name, email, password }; // In real app, hash password!
|
||||||
|
users.push(newUser);
|
||||||
|
db.set('users', users);
|
||||||
|
|
||||||
|
localStorage.setItem('mindshift_currentUser', JSON.stringify(newUser));
|
||||||
|
return { token: 'offline-token', user: newUser };
|
||||||
|
},
|
||||||
|
|
||||||
|
async login(email, password) {
|
||||||
|
await new Promise(r => setTimeout(r, 500));
|
||||||
|
|
||||||
|
const users = db.get('users');
|
||||||
|
const user = users.find(u => u.email === email && u.password === password);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('Invalid credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('mindshift_currentUser', JSON.stringify(user));
|
||||||
|
return { token: 'offline-token', user };
|
||||||
|
},
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
localStorage.removeItem('mindshift_currentUser');
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProfile() {
|
||||||
|
return getCurrentUser();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Mood API ---
|
||||||
|
export const moodAPI = {
|
||||||
|
async trackMood(moodType, intensity, notes) {
|
||||||
|
return db.add('moods', { mood_type: moodType, intensity, notes });
|
||||||
|
},
|
||||||
|
|
||||||
|
async getMoodHistory() {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (!user) return [];
|
||||||
|
const moods = db.get('moods').filter(m => m.user_id === user.id);
|
||||||
|
return moods.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Thought Record API ---
|
||||||
|
export const thoughtAPI = {
|
||||||
|
async saveThoughtRecord(thoughtData) {
|
||||||
|
// Map frontend keys to DB keys if needed, but for offline we can store as is
|
||||||
|
return db.add('thoughts', thoughtData);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getThoughtRecords() {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (!user) return [];
|
||||||
|
const thoughts = db.get('thoughts').filter(t => t.user_id === user.id);
|
||||||
|
return thoughts.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateThoughtRecord(id, data) {
|
||||||
|
return db.update('thoughts', id, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteThoughtRecord(id) {
|
||||||
|
return db.remove('thoughts', id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Gratitude API ---
|
||||||
|
export const gratitudeAPI = {
|
||||||
|
async saveGratitudeEntry(entry) {
|
||||||
|
// Entry object comes as { entries: [], date: ... }
|
||||||
|
// We'll store individual entries or the whole block.
|
||||||
|
// Let's store the block to match frontend expectation
|
||||||
|
return db.add('gratitude', entry);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getGratitudeEntries() {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (!user) return [];
|
||||||
|
const entries = db.get('gratitude').filter(g => g.user_id === user.id);
|
||||||
|
return entries.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Progress API ---
|
||||||
|
export const progressAPI = {
|
||||||
|
async getProgressStats() {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (!user) return { today: {}, week: {}, totals: {} };
|
||||||
|
|
||||||
|
const moods = db.get('moods').filter(m => m.user_id === user.id);
|
||||||
|
const gratitude = db.get('gratitude').filter(g => g.user_id === user.id);
|
||||||
|
const exercises = db.get('exercises').filter(e => e.user_id === user.id);
|
||||||
|
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const todayMoods = moods.filter(m => m.created_at.startsWith(today));
|
||||||
|
|
||||||
|
// Calculate stats
|
||||||
|
const todayAvg = todayMoods.length > 0
|
||||||
|
? todayMoods.reduce((sum, m) => sum + parseInt(m.intensity), 0) / todayMoods.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
today: { mood_score: todayAvg.toFixed(1) },
|
||||||
|
totals: {
|
||||||
|
totalSessions: exercises.length,
|
||||||
|
totalGratitude: gratitude.length
|
||||||
|
},
|
||||||
|
week: { avgMood: 0 } // simplified for offline
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async getProgressHistory(days = 7) {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (!user) return [];
|
||||||
|
|
||||||
|
const moods = db.get('moods').filter(m => m.user_id === user.id);
|
||||||
|
// Group by date and avg score
|
||||||
|
const history = [];
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - i);
|
||||||
|
const dateStr = date.toISOString().split('T')[0];
|
||||||
|
|
||||||
|
const dayMoods = moods.filter(m => m.created_at.startsWith(dateStr));
|
||||||
|
const score = dayMoods.length > 0
|
||||||
|
? dayMoods.reduce((sum, m) => sum + parseInt(m.intensity), 0) / dayMoods.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
history.push({ date: dateStr, mood_score: score });
|
||||||
|
}
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Exercise API ---
|
||||||
|
export const exerciseAPI = {
|
||||||
|
async logSession(type, duration) {
|
||||||
|
return db.add('exercises', { exercise_type: type, duration });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Notification API ---
|
||||||
|
export const notificationAPI = {
|
||||||
|
async getNotifications() {
|
||||||
|
const user = getCurrentUser();
|
||||||
|
if (!user) return [];
|
||||||
|
return db.get('notifications').filter(n => n.user_id === user.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async markAsRead(id) {
|
||||||
|
return db.update('notifications', id, { read: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteNotification(id) {
|
||||||
|
return db.remove('notifications', id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async addNotification(notification) {
|
||||||
|
return db.add('notifications', notification);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isAuthenticated() {
|
||||||
|
return !!getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeAPI() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -1,29 +1,36 @@
|
|||||||
/* Base Styles & Variables */
|
/* Base Styles & Variables */
|
||||||
:root {
|
:root {
|
||||||
--primary: #FF6B6B;
|
/* Relaxing Palette */
|
||||||
--primary-light: #FF8E8E;
|
--primary: #6B9080; /* Soft Sage Green */
|
||||||
--primary-dark: #E55555;
|
--primary-light: #A4C3B2;
|
||||||
--primary-container: #FFE5E5;
|
--primary-dark: #3A5A4A;
|
||||||
|
--primary-container: #EAF4F0;
|
||||||
--on-primary: #FFFFFF;
|
--on-primary: #FFFFFF;
|
||||||
--on-primary-container: #410002;
|
--on-primary-container: #1C3329;
|
||||||
--secondary: #FFB74D;
|
|
||||||
--secondary-container: #FFF3E0;
|
--secondary: #8ECAE6; /* Soft Sky Blue */
|
||||||
|
--secondary-container: #E1F5FE;
|
||||||
--on-secondary: #FFFFFF;
|
--on-secondary: #FFFFFF;
|
||||||
--on-secondary-container: #4E2B00;
|
--on-secondary-container: #004D61;
|
||||||
--tertiary: #4FC3F7;
|
|
||||||
--tertiary-container: #E1F5FE;
|
--tertiary: #B8B8FF; /* Gentle Lavender */
|
||||||
--surface: rgba(255, 255, 255, 0.9);
|
--tertiary-container: #EFEEFF;
|
||||||
--surface-variant: #F5F5F5;
|
|
||||||
--on-surface: #212121;
|
--surface: rgba(255, 255, 255, 0.92);
|
||||||
--on-surface-variant: #757575;
|
--surface-variant: #F4F7F6; /* Very soft cool grey */
|
||||||
--outline: #BDBDBD;
|
--on-surface: #2C3E50;
|
||||||
--shadow: rgba(0,0,0,0.1);
|
--on-surface-variant: #607D8B;
|
||||||
--error: #FF5252;
|
|
||||||
--success: #66BB6A;
|
--outline: #CFD8DC;
|
||||||
--warning: #FFA726;
|
--shadow: rgba(44, 62, 80, 0.1);
|
||||||
--joy: #AB47BC;
|
|
||||||
--peace: #26A69A;
|
--error: #E57373;
|
||||||
--energy: #FFEE58;
|
--success: #81C784;
|
||||||
|
--warning: #FFB74D;
|
||||||
|
|
||||||
|
--joy: #9575CD;
|
||||||
|
--peace: #4DB6AC;
|
||||||
|
--energy: #FFF176;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -34,9 +41,9 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #FF6B6B 100%);
|
background: linear-gradient(135deg, #FDFBF7 0%, #E6F3F0 100%); /* Relaxing Cream to Sage */
|
||||||
background-size: 400% 400%;
|
background-size: 400% 400%;
|
||||||
animation: gradientBG 15s ease infinite;
|
animation: gradientBG 20s ease infinite;
|
||||||
color: var(--on-surface);
|
color: var(--on-surface);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -113,7 +120,7 @@ body {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(135deg, #FF6B6B 0%, #4ECDC4 100%);
|
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -159,10 +166,10 @@ body {
|
|||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
.app-header {
|
.app-header {
|
||||||
background-color: rgba(255, 107, 107, 0.95);
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
color: var(--on-primary);
|
color: var(--primary);
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
|
box-shadow: 0 4px 20px rgba(0,0,0,0.05);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
@@ -171,8 +178,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-header:hover {
|
.app-header:hover {
|
||||||
background-color: rgba(255, 107, 107, 1);
|
background-color: rgba(255, 255, 255, 1);
|
||||||
box-shadow: 0 6px 24px rgba(0,0,0,0.15);
|
box-shadow: 0 6px 24px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
@@ -189,12 +196,16 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions button {
|
||||||
|
color: var(--primary) !important; /* Override inline style */
|
||||||
|
}
|
||||||
|
|
||||||
/* Notification Badge Pulse */
|
/* Notification Badge Pulse */
|
||||||
.notification-badge {
|
.notification-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
background: var(--secondary);
|
background: var(--error);
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -238,15 +249,119 @@ body {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 255, 255, 0.98);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(20px);
|
||||||
box-shadow: 0 -4px 20px rgba(0,0,0,0.1);
|
box-shadow: 0 -4px 30px rgba(0,0,0,0.05);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
|
/* Android Safe Area Fix - Significantly Increased Padding */
|
||||||
|
padding-bottom: calc(40px + env(safe-area-inset-bottom));
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
border-top-left-radius: 20px;
|
border-top-left-radius: 24px;
|
||||||
border-top-right-radius: 20px;
|
border-top-right-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Guided Relaxation Styles */
|
||||||
|
.guided-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: radial-gradient(circle at center, #2E7D32, #004D40);
|
||||||
|
z-index: 6000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-step-icon {
|
||||||
|
font-size: 80px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
animation: floatIcon 3s ease-in-out infinite;
|
||||||
|
filter: drop-shadow(0 0 20px rgba(255,255,255,0.3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatIcon {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-15px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-instruction {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-sub {
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-progress-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-dot.active {
|
||||||
|
background: white;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-action-btn {
|
||||||
|
background: white;
|
||||||
|
color: #004D40;
|
||||||
|
border: none;
|
||||||
|
padding: 16px 40px;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-action-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guided-controls {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
@@ -299,7 +414,8 @@ body {
|
|||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding-bottom: 100px;
|
/* Increased bottom padding to prevent content overlap with taller nav */
|
||||||
|
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||||
min-height: calc(100vh - 120px);
|
min-height: calc(100vh - 120px);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -381,7 +497,7 @@ body {
|
|||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
background: linear-gradient(135deg, var(--primary-container), white);
|
background: linear-gradient(135deg, var(--primary-container), white);
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.2);
|
box-shadow: 0 0 0 4px rgba(107, 144, 128, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mood-emoji {
|
.mood-emoji {
|
||||||
@@ -401,6 +517,97 @@ body {
|
|||||||
100% { transform: scale(1.2); }
|
100% { transform: scale(1.2); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Progress Section */
|
||||||
|
.progress-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card {
|
||||||
|
background-color: var(--surface-variant);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-value {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chart Container */
|
||||||
|
.chart-container {
|
||||||
|
position: relative;
|
||||||
|
height: 250px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Analytics Cards */
|
||||||
|
.analytics-card {
|
||||||
|
background: rgba(255, 255, 255, 0.6);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.analytics-card h4 {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: var(--primary);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-value {
|
||||||
|
width: 40px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-progress {
|
||||||
|
flex: 2;
|
||||||
|
height: 6px;
|
||||||
|
background: var(--surface-variant);
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 0 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emotion-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--primary);
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
/* Buttons - Alive */
|
/* Buttons - Alive */
|
||||||
.btn {
|
.btn {
|
||||||
padding: 16px 32px;
|
padding: 16px 32px;
|
||||||
@@ -440,19 +647,19 @@ body {
|
|||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
||||||
color: white;
|
color: white;
|
||||||
box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4);
|
box-shadow: 0 8px 20px rgba(107, 144, 128, 0.4);
|
||||||
animation: breathBtn 3s infinite ease-in-out;
|
animation: breathBtn 3s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes breathBtn {
|
@keyframes breathBtn {
|
||||||
0% { transform: scale(1); box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); }
|
0% { transform: scale(1); box-shadow: 0 8px 20px rgba(107, 144, 128, 0.4); }
|
||||||
50% { transform: scale(1.02); box-shadow: 0 12px 24px rgba(255, 107, 107, 0.6); }
|
50% { transform: scale(1.02); box-shadow: 0 12px 24px rgba(107, 144, 128, 0.6); }
|
||||||
100% { transform: scale(1); box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); }
|
100% { transform: scale(1); box-shadow: 0 8px 20px rgba(107, 144, 128, 0.4); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
transform: translateY(-4px) scale(1.05);
|
transform: translateY(-4px) scale(1.05);
|
||||||
box-shadow: 0 15px 30px rgba(255, 107, 107, 0.5);
|
box-shadow: 0 15px 30px rgba(107, 144, 128, 0.5);
|
||||||
animation: none; /* Stop breathing on hover to focus */
|
animation: none; /* Stop breathing on hover to focus */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,20 +697,20 @@ body {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 4px 10px rgba(255, 107, 107, 0.4);
|
box-shadow: 0 4px 10px rgba(107, 144, 128, 0.4);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider:hover::-webkit-slider-thumb {
|
.slider:hover::-webkit-slider-thumb {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
box-shadow: 0 6px 15px rgba(255, 107, 107, 0.6);
|
box-shadow: 0 6px 15px rgba(107, 144, 128, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea, input[type="text"] {
|
textarea, input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
border: 2px solid rgba(0,0,0,0.1);
|
border: 2px solid rgba(0,0,0,0.05);
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.9);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
@@ -513,7 +720,7 @@ textarea, input[type="text"] {
|
|||||||
textarea:focus, input[type="text"]:focus {
|
textarea:focus, input[type="text"]:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.1);
|
box-shadow: 0 0 0 4px rgba(107, 144, 128, 0.1);
|
||||||
transform: scale(1.01);
|
transform: scale(1.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -607,7 +814,7 @@ textarea:focus, input[type="text"]:focus {
|
|||||||
/* FAB - Alive */
|
/* FAB - Alive */
|
||||||
.fab {
|
.fab {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 90px;
|
bottom: 110px; /* Increased from 90px */
|
||||||
right: 24px;
|
right: 24px;
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
@@ -615,7 +822,7 @@ textarea:focus, input[type="text"]:focus {
|
|||||||
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.5);
|
box-shadow: 0 8px 25px rgba(107, 144, 128, 0.5);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -626,7 +833,7 @@ textarea:focus, input[type="text"]:focus {
|
|||||||
|
|
||||||
.fab:hover {
|
.fab:hover {
|
||||||
transform: scale(1.15) rotate(90deg);
|
transform: scale(1.15) rotate(90deg);
|
||||||
box-shadow: 0 12px 35px rgba(255, 107, 107, 0.6);
|
box-shadow: 0 12px 35px rgba(107, 144, 128, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab .material-icons {
|
.fab .material-icons {
|
||||||
@@ -667,8 +874,490 @@ textarea:focus, input[type="text"]:focus {
|
|||||||
.nav-label { font-size: 10px; }
|
.nav-label { font-size: 10px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Exercise Modals */
|
||||||
|
.exercise-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 2000;
|
||||||
|
animation: zoomIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-modal .card {
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zoomIn {
|
||||||
|
from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
||||||
|
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-modal h3 {
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercise-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
/* Desktop Sidebar */
|
/* Desktop Sidebar */
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
.bottom-nav { display: none; }
|
.bottom-nav {
|
||||||
/* ... (keep existing desktop styles if needed, but adapt to new look) ... */
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
top: 80px;
|
||||||
|
left: 20px;
|
||||||
|
right: auto;
|
||||||
|
bottom: 20px;
|
||||||
|
width: 100px;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 20px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-top: 40px;
|
||||||
|
gap: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
margin-left: 120px;
|
||||||
|
max-width: calc(100% - 140px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.active::after {
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
bottom: auto;
|
||||||
|
width: 4px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* History Lists */
|
||||||
|
.history-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-type {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-details {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--on-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-notes {
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: var(--error);
|
||||||
|
background: rgba(255, 82, 82, 0.1);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading Spinner */
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid rgba(0,0,0,0.1);
|
||||||
|
border-left-color: var(--primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
/* Auth Modal Styles */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 3000;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 30px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||||
|
text-align: center;
|
||||||
|
animation: slideInUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animated gradient border top */
|
||||||
|
.modal-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
background: linear-gradient(90deg, var(--primary), var(--secondary), var(--tertiary));
|
||||||
|
animation: gradientFlow 3s linear infinite;
|
||||||
|
background-size: 200% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientFlow {
|
||||||
|
0% { background-position: 100% 0; }
|
||||||
|
100% { background-position: -100% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-title {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
background: linear-gradient(45deg, var(--primary), var(--secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
animation: pulseText 3s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulseText {
|
||||||
|
0%, 100% { transform: scale(1); filter: brightness(100%); }
|
||||||
|
50% { transform: scale(1.05); filter: brightness(110%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 2px solid rgba(0,0,0,0.05);
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
background: white;
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 4px rgba(107, 144, 128, 0.1);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-form {
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--on-surface-variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-form a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-left: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-form a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--primary);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
transform-origin: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch-form a:hover::after {
|
||||||
|
transform: scaleX(1);
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smart Breathing Enhanced Styles */
|
||||||
|
.breathing-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: radial-gradient(circle at center, #1a2a6c, #b21f1f, #fdbb2d); /* Deep calming gradient */
|
||||||
|
background: radial-gradient(circle at center, #2b5876, #4e4376); /* Deep calming blue/purple */
|
||||||
|
z-index: 5000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breathing-visual-container {
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breath-circle-main {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: 0 0 40px rgba(255, 255, 255, 0.2);
|
||||||
|
position: absolute;
|
||||||
|
transition: transform 0.1s linear, background-color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breath-circle-inner {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
position: absolute;
|
||||||
|
box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
|
||||||
|
transition: transform 0.1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breath-particles {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
animation: rotateParticles 20s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breath-instruction-text {
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
min-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breath-sub-text {
|
||||||
|
font-size: 18px;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.technique-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 10px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.technique-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 30px;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.technique-btn.active {
|
||||||
|
background: white;
|
||||||
|
color: #4e4376;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breath-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-btn-icon:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateParticles {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific States */
|
||||||
|
.inhale .breath-circle-main { background: rgba(100, 255, 218, 0.3); }
|
||||||
|
.hold .breath-circle-main { background: rgba(255, 235, 59, 0.3); }
|
||||||
|
.exhale .breath-circle-main { background: rgba(255, 107, 107, 0.3); }
|
||||||
|
|
||||||
|
/* RTL Support Overrides */
|
||||||
|
html[dir="rtl"] {
|
||||||
|
text-align: right;
|
||||||
|
--direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .fab {
|
||||||
|
right: auto;
|
||||||
|
left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .notification-badge {
|
||||||
|
right: auto;
|
||||||
|
left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .notification-dropdown {
|
||||||
|
right: auto;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .card-title::after {
|
||||||
|
transform-origin: right;
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .switch-form a {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .form-group label {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .guided-controls {
|
||||||
|
right: auto;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust history item hover transform for RTL */
|
||||||
|
html[dir="rtl"] .history-item:hover {
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust emotion value alignment */
|
||||||
|
html[dir="rtl"] .emotion-value {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust desktop sidebar for RTL */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
html[dir="rtl"] .bottom-nav {
|
||||||
|
left: auto;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .main-content {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .nav-item.active::after {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
504
MindShift-Windows/src/translations.js
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
export const translations = {
|
||||||
|
en: {
|
||||||
|
// Meta
|
||||||
|
"app_title": "MindShift",
|
||||||
|
"app_subtitle": "Your personal CBT companion",
|
||||||
|
"init_loading": "Initializing MindShift...",
|
||||||
|
"init_error": "Error Loading App",
|
||||||
|
"proactive_badge": "✨ Time for a vibe check?",
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
"nav_home": "Home",
|
||||||
|
"nav_mood": "Mood",
|
||||||
|
"nav_thoughts": "Thoughts",
|
||||||
|
"nav_gratitude": "Gratitude",
|
||||||
|
"nav_progress": "Progress",
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
"auth_welcome": "Welcome to MindShift",
|
||||||
|
"auth_login": "Login",
|
||||||
|
"auth_register": "Register",
|
||||||
|
"auth_name": "Name",
|
||||||
|
"auth_email": "Email",
|
||||||
|
"auth_password": "Password",
|
||||||
|
"auth_no_account": "Don't have an account?",
|
||||||
|
"auth_has_account": "Already have an account?",
|
||||||
|
"auth_login_failed": "Login failed",
|
||||||
|
"auth_reg_failed": "Registration failed",
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home_welcome": "Welcome Back! 🌟",
|
||||||
|
"home_subtitle": "Ready to shift your mind?",
|
||||||
|
"home_log_mood": "Log Mood",
|
||||||
|
"home_record_thought": "Record Thought",
|
||||||
|
"home_gratitude": "Gratitude",
|
||||||
|
"home_daily_vibe": "Daily Vibe Check 📊",
|
||||||
|
"home_quick_relief": "Quick Relief 🌿",
|
||||||
|
"home_breathe": "Breathe",
|
||||||
|
"home_relax": "Relax",
|
||||||
|
"home_stats_mood": "Today's Mood",
|
||||||
|
"home_stats_sessions": "Sessions",
|
||||||
|
"home_stats_avg": "Weekly Avg",
|
||||||
|
"home_stats_gratitude": "Gratitude",
|
||||||
|
"home_retry": "Tap to Retry",
|
||||||
|
|
||||||
|
// Mood
|
||||||
|
"mood_title": "How are you feeling?",
|
||||||
|
"mood_joy": "Joy",
|
||||||
|
"mood_peace": "Peace",
|
||||||
|
"mood_energy": "Energy",
|
||||||
|
"mood_anxiety": "Anxiety",
|
||||||
|
"mood_sadness": "Sadness",
|
||||||
|
"mood_anger": "Anger",
|
||||||
|
"mood_intensity": "Intensity",
|
||||||
|
"mood_notes_placeholder": "Any thoughts?",
|
||||||
|
"mood_save": "Save Mood",
|
||||||
|
"mood_saved_success": "Mood tracked successfully!",
|
||||||
|
"mood_select_warning": "Please select a mood",
|
||||||
|
|
||||||
|
// Thoughts
|
||||||
|
"thought_title": "Thought Record 🧠",
|
||||||
|
"thought_situation": "Situation (Who, what, where, when?)",
|
||||||
|
"thought_automatic": "Automatic Thoughts (What went through your mind?)",
|
||||||
|
"thought_emotions": "Emotions",
|
||||||
|
"thought_add_emotion": "+ Add Emotion",
|
||||||
|
"thought_evidence": "Evidence For/Against",
|
||||||
|
"thought_alternative": "Alternative Thought",
|
||||||
|
"thought_save": "Save Record",
|
||||||
|
"thought_saved_success": "Thought record saved successfully!",
|
||||||
|
"thought_fill_warning": "Please fill in at least the situation and thoughts",
|
||||||
|
|
||||||
|
// Gratitude
|
||||||
|
"gratitude_title": "Gratitude Journal 🙏",
|
||||||
|
"gratitude_intro": "List 3 things you are grateful for today:",
|
||||||
|
"gratitude_placeholder": "I am grateful for...",
|
||||||
|
"gratitude_add": "+ Add Another",
|
||||||
|
"gratitude_save": "Save Entry",
|
||||||
|
"gratitude_saved_success": "Gratitude entries saved successfully!",
|
||||||
|
"gratitude_empty_warning": "Please add at least one gratitude entry",
|
||||||
|
|
||||||
|
// Progress & History
|
||||||
|
"progress_title": "Your Progress 📈",
|
||||||
|
"progress_weekly": "Weekly Mood 📅",
|
||||||
|
"progress_history": "Recent History 📜",
|
||||||
|
"history_tab_moods": "Moods",
|
||||||
|
"history_tab_thoughts": "Thoughts",
|
||||||
|
"history_tab_gratitude": "Gratitude",
|
||||||
|
"history_empty_mood": "No mood entries yet. Start tracking! 📝",
|
||||||
|
"history_empty_thoughts": "No thought records yet. 🧠",
|
||||||
|
"history_empty_gratitude": "No gratitude entries yet. 🙏",
|
||||||
|
"history_select_prompt": "Select a category to view history",
|
||||||
|
|
||||||
|
// Quick Actions
|
||||||
|
"quick_title": "Quick Actions ⚡",
|
||||||
|
"quick_relax_now": "Relax Now",
|
||||||
|
"close": "Close",
|
||||||
|
|
||||||
|
// Guided Relaxation
|
||||||
|
"guided_title": "Mindfulness Session",
|
||||||
|
"guided_select_mode": "Select a Session",
|
||||||
|
"mode_grounding": "Grounding (5-4-3-2-1)",
|
||||||
|
"mode_body_scan": "Body Scan",
|
||||||
|
"mode_visualization": "Visualization",
|
||||||
|
|
||||||
|
"guided_pre_stress": "How stressed are you now?",
|
||||||
|
"guided_post_stress": "How do you feel now?",
|
||||||
|
"guided_start": "Start Session",
|
||||||
|
"guided_settings": "Settings",
|
||||||
|
"guided_ambience": "Background Sounds",
|
||||||
|
"guided_voice": "Voice Guide",
|
||||||
|
"guided_summary_title": "Session Complete",
|
||||||
|
"guided_summary_reduced": "Stress Reduced by",
|
||||||
|
"guided_summary_maintained": "You maintained your calm.",
|
||||||
|
|
||||||
|
// Grounding
|
||||||
|
"guided_sight_title": "Sight",
|
||||||
|
"guided_sight_instruction": "Look around you.",
|
||||||
|
"guided_sight_sub": "Find 5 things you can see.",
|
||||||
|
"guided_touch_title": "Touch",
|
||||||
|
"guided_touch_instruction": "Feel the textures.",
|
||||||
|
"guided_touch_sub": "Find 4 things you can touch.",
|
||||||
|
"guided_sound_title": "Sound",
|
||||||
|
"guided_sound_instruction": "Listen carefully.",
|
||||||
|
"guided_sound_sub": "Identify 3 sounds you hear.",
|
||||||
|
"guided_smell_title": "Smell",
|
||||||
|
"guided_smell_instruction": "Breathe in deep.",
|
||||||
|
"guided_smell_sub": "Notice 2 things you can smell.",
|
||||||
|
"guided_taste_title": "Taste",
|
||||||
|
"guided_taste_instruction": "Focus on your mouth.",
|
||||||
|
"guided_taste_sub": "Find 1 thing you can taste.",
|
||||||
|
"guided_found_btn": "I found one",
|
||||||
|
"guided_complete_title": "You did great!",
|
||||||
|
"guided_complete_sub": "Feeling more grounded?",
|
||||||
|
"guided_complete_btn": "Complete",
|
||||||
|
|
||||||
|
// Body Scan Script
|
||||||
|
"scan_intro": "Find a comfortable position and close your eyes. Let's begin by taking a few deep breaths.",
|
||||||
|
"scan_feet": "Bring your awareness to your feet. Notice any sensations of warmth, coolness, or pressure. Let them soften.",
|
||||||
|
"scan_legs": "Move your attention up to your calves and thighs. If you notice any tension, imagine it melting away with your exhale.",
|
||||||
|
"scan_stomach": "Focus on your belly. Feel it rise gently as you inhale, and fall as you exhale. Soften your stomach muscles.",
|
||||||
|
"scan_chest": "Bring your attention to your chest and heart center. Notice the rhythm of your breath. Let your shoulders drop down away from your ears.",
|
||||||
|
"scan_shoulders": "Notice your neck and throat. Let go of any tightness here. Allow your jaw to unhinge slightly.",
|
||||||
|
"scan_face": "Soften the muscles around your eyes and forehead. Let your entire face be smooth and relaxed.",
|
||||||
|
"scan_outro": "Take a moment to feel your whole body, resting in this state of relaxation. When you are ready, gently wiggle your fingers and toes, and open your eyes.",
|
||||||
|
|
||||||
|
// Visualization Script
|
||||||
|
"vis_intro": "Close your eyes and take a deep breath. We are going to take a journey to a peaceful place.",
|
||||||
|
"vis_step1": "Imagine you are standing at the edge of a lush, ancient forest. The trees are tall and protective. You feel safe here.",
|
||||||
|
"vis_step2": "As you walk deeper into the woods, the air becomes cool and fresh. You can smell the scent of pine and damp earth.",
|
||||||
|
"vis_step3": "Sunlight filters through the canopy above, creating dappled patterns of light on the soft mossy path beneath your feet.",
|
||||||
|
"vis_step4": "In the distance, you hear the gentle sound of a stream flowing over smooth stones. The sound is rhythmic and calming.",
|
||||||
|
"vis_outro": "Take a moment to absorb the peace of this place. Know that you can return here anytime. Slowly bring your awareness back to the room and open your eyes.",
|
||||||
|
|
||||||
|
// Smart Breathing
|
||||||
|
"breath_balance": "Balance",
|
||||||
|
"breath_relax": "Relax",
|
||||||
|
"breath_focus": "Focus",
|
||||||
|
"breath_in": "Breathe In",
|
||||||
|
"breath_out": "Breathe Out",
|
||||||
|
"breath_hold": "Hold",
|
||||||
|
"breath_ready": "Get Ready...",
|
||||||
|
"breath_sit": "Sit comfortably",
|
||||||
|
"breath_complete": "Breathing session complete! 🌬️",
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
"notifications_empty": "No notifications",
|
||||||
|
"just_now": "Just now",
|
||||||
|
"ago_m": "m ago",
|
||||||
|
"ago_h": "h ago"
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
|
// Meta
|
||||||
|
"app_title": "MindShift",
|
||||||
|
"app_subtitle": "Ваш личный помощник КПТ",
|
||||||
|
"init_loading": "Загрузка MindShift...",
|
||||||
|
"init_error": "Ошибка загрузки",
|
||||||
|
"proactive_badge": "✨ Время проверить настроение?",
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
"nav_home": "Главная",
|
||||||
|
"nav_mood": "Настроение",
|
||||||
|
"nav_thoughts": "Мысли",
|
||||||
|
"nav_gratitude": "Благодарность",
|
||||||
|
"nav_progress": "Прогресс",
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
"auth_welcome": "Добро пожаловать в MindShift",
|
||||||
|
"auth_login": "Войти",
|
||||||
|
"auth_register": "Регистрация",
|
||||||
|
"auth_name": "Имя",
|
||||||
|
"auth_email": "Email",
|
||||||
|
"auth_password": "Пароль",
|
||||||
|
"auth_no_account": "Нет аккаунта?",
|
||||||
|
"auth_has_account": "Уже есть аккаунт?",
|
||||||
|
"auth_login_failed": "Ошибка входа",
|
||||||
|
"auth_reg_failed": "Ошибка регистрации",
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home_welcome": "С возвращением! 🌟",
|
||||||
|
"home_subtitle": "Готовы изменить мышление?",
|
||||||
|
"home_log_mood": "Настроение",
|
||||||
|
"home_record_thought": "Запись мыслей",
|
||||||
|
"home_gratitude": "Благодарность",
|
||||||
|
"home_daily_vibe": "Статистика дня 📊",
|
||||||
|
"home_quick_relief": "Быстрая помощь 🌿",
|
||||||
|
"home_breathe": "Дыхание",
|
||||||
|
"home_relax": "Релакс",
|
||||||
|
"home_stats_mood": "Настроение",
|
||||||
|
"home_stats_sessions": "Сессии",
|
||||||
|
"home_stats_avg": "Ср. за неделю",
|
||||||
|
"home_stats_gratitude": "Благодарности",
|
||||||
|
"home_retry": "Повторить",
|
||||||
|
|
||||||
|
// Mood
|
||||||
|
"mood_title": "Как вы себя чувствуете?",
|
||||||
|
"mood_joy": "Радость",
|
||||||
|
"mood_peace": "Покой",
|
||||||
|
"mood_energy": "Энергия",
|
||||||
|
"mood_anxiety": "Тревога",
|
||||||
|
"mood_sadness": "Грусть",
|
||||||
|
"mood_anger": "Гнев",
|
||||||
|
"mood_intensity": "Интенсивность",
|
||||||
|
"mood_notes_placeholder": "О чем думаете?",
|
||||||
|
"mood_save": "Сохранить",
|
||||||
|
"mood_saved_success": "Настроение сохранено!",
|
||||||
|
"mood_select_warning": "Выберите настроение",
|
||||||
|
|
||||||
|
// Thoughts
|
||||||
|
"thought_title": "Дневник мыслей 🧠",
|
||||||
|
"thought_situation": "Ситуация (Кто, что, где, когда?)",
|
||||||
|
"thought_automatic": "Автоматические мысли (Что пришло в голову?)",
|
||||||
|
"thought_emotions": "Эмоции",
|
||||||
|
"thought_add_emotion": "+ Добавить эмоцию",
|
||||||
|
"thought_evidence": "За и Против",
|
||||||
|
"thought_alternative": "Альтернативная мысль",
|
||||||
|
"thought_save": "Сохранить запись",
|
||||||
|
"thought_saved_success": "Запись сохранена!",
|
||||||
|
"thought_fill_warning": "Заполните ситуацию и мысли",
|
||||||
|
|
||||||
|
// Gratitude
|
||||||
|
"gratitude_title": "Дневник благодарности 🙏",
|
||||||
|
"gratitude_intro": "3 вещи, за которые вы благодарны:",
|
||||||
|
"gratitude_placeholder": "Я благодарен за...",
|
||||||
|
"gratitude_add": "+ Добавить еще",
|
||||||
|
"gratitude_save": "Сохранить",
|
||||||
|
"gratitude_saved_success": "Записи сохранены!",
|
||||||
|
"gratitude_empty_warning": "Добавьте хотя бы одну запись",
|
||||||
|
|
||||||
|
// Progress & History
|
||||||
|
"progress_title": "Ваш прогресс 📈",
|
||||||
|
"progress_weekly": "Настроение за неделю 📅",
|
||||||
|
"progress_history": "История 📜",
|
||||||
|
"history_tab_moods": "Настроение",
|
||||||
|
"history_tab_thoughts": "Мысли",
|
||||||
|
"history_tab_gratitude": "Благодарность",
|
||||||
|
"history_empty_mood": "Нет записей. Начните отслеживать! 📝",
|
||||||
|
"history_empty_thoughts": "Нет записей мыслей. 🧠",
|
||||||
|
"history_empty_gratitude": "Нет записей благодарности. 🙏",
|
||||||
|
"history_select_prompt": "Выберите категорию для просмотра",
|
||||||
|
|
||||||
|
// Quick Actions
|
||||||
|
"quick_title": "Быстрые действия ⚡",
|
||||||
|
"quick_relax_now": "Релакс сейчас",
|
||||||
|
"close": "Закрыть",
|
||||||
|
|
||||||
|
// Guided Relaxation
|
||||||
|
"guided_title": "Сессия осознанности",
|
||||||
|
"guided_select_mode": "Выберите сессию",
|
||||||
|
"mode_grounding": "Заземление (5-4-3-2-1)",
|
||||||
|
"mode_body_scan": "Сканирование тела",
|
||||||
|
"mode_visualization": "Визуализация",
|
||||||
|
|
||||||
|
"guided_pre_stress": "Ваш уровень стресса?",
|
||||||
|
"guided_post_stress": "Как вы себя чувствуете?",
|
||||||
|
"guided_start": "Начать сессию",
|
||||||
|
"guided_settings": "Настройки",
|
||||||
|
"guided_ambience": "Фоновые звуки",
|
||||||
|
"guided_voice": "Голос гида",
|
||||||
|
"guided_summary_title": "Сессия завершена",
|
||||||
|
"guided_summary_reduced": "Стресс снижен на",
|
||||||
|
"guided_summary_maintained": "Вы сохранили спокойствие.",
|
||||||
|
|
||||||
|
"guided_sight_title": "Зрение",
|
||||||
|
"guided_sight_instruction": "Оглянитесь вокруг.",
|
||||||
|
"guided_sight_sub": "Найдите 5 вещей, которые вы видите.",
|
||||||
|
"guided_touch_title": "Осязание",
|
||||||
|
"guided_touch_instruction": "Почувствуйте текстуры.",
|
||||||
|
"guided_touch_sub": "Найдите 4 вещи, которые можно потрогать.",
|
||||||
|
"guided_sound_title": "Слух",
|
||||||
|
"guided_sound_instruction": "Прислушайтесь.",
|
||||||
|
"guided_sound_sub": "Найдите 3 звука, которые вы слышите.",
|
||||||
|
"guided_smell_title": "Обоняние",
|
||||||
|
"guided_smell_instruction": "Сделайте глубокий вдох.",
|
||||||
|
"guided_smell_sub": "Найдите 2 запаха.",
|
||||||
|
"guided_taste_title": "Вкус",
|
||||||
|
"guided_taste_instruction": "Сосредоточьтесь на вкусе.",
|
||||||
|
"guided_taste_sub": "Найдите 1 вещь, которую можно попробовать.",
|
||||||
|
"guided_found_btn": "Найдено",
|
||||||
|
"guided_complete_title": "Отлично!",
|
||||||
|
"guided_complete_sub": "Чувствуете себя спокойнее?",
|
||||||
|
"guided_complete_btn": "Завершить",
|
||||||
|
|
||||||
|
// Body Scan Script
|
||||||
|
"scan_intro": "Примите удобное положение и закройте глаза. Давайте начнем с нескольких глубоких вдохов.",
|
||||||
|
"scan_feet": "Сосредоточьтесь на ногах. Почувствуйте их вес, тепло или холод. Позвольте им расслабиться.",
|
||||||
|
"scan_legs": "Переведите внимание на голени и бедра. Если есть напряжение, представьте, как оно уходит с выдохом.",
|
||||||
|
"scan_stomach": "Заметьте свой живот. Почувствуйте, как он мягко поднимается при вдохе и опускается при выдохе.",
|
||||||
|
"scan_chest": "Перенесите внимание на грудь и сердце. Заметьте ритм дыхания. Опустите плечи.",
|
||||||
|
"scan_shoulders": "Заметьте шею и горло. Отпустите любое напряжение. Слегка разомкните челюсти.",
|
||||||
|
"scan_face": "Расслабьте мышцы вокруг глаз и лба. Пусть все лицо станет гладким и спокойным.",
|
||||||
|
"scan_outro": "Почувствуйте все свое тело в состоянии покоя. Когда будете готовы, пошевелите пальцами и откройте глаза.",
|
||||||
|
|
||||||
|
// Visualization Script
|
||||||
|
"vis_intro": "Закройте глаза и сделайте глубокий вдох. Мы отправимся в путешествие в спокойное место.",
|
||||||
|
"vis_step1": "Представьте, что вы стоите на краю тихого древнего леса. Деревья высокие и защищают вас. Здесь безопасно.",
|
||||||
|
"vis_step2": "Вы идете вглубь леса, воздух становится прохладным и свежим. Вы чувствуете запах хвои и влажной земли.",
|
||||||
|
"vis_step3": "Солнечный свет пробивается сквозь листву, создавая узоры света на мягкой моховой тропинке под ногами.",
|
||||||
|
"vis_step4": "Вдали вы слышите тихое журчание ручья, текущего по гладким камням. Этот звук ритмичный и успокаивающий.",
|
||||||
|
"vis_outro": "Впитайте покой этого места. Знайте, что можете вернуться сюда в любой момент. Медленно откройте глаза.",
|
||||||
|
|
||||||
|
// Smart Breathing
|
||||||
|
"breath_balance": "Баланс",
|
||||||
|
"breath_relax": "Релакс",
|
||||||
|
"breath_focus": "Фокус",
|
||||||
|
"breath_in": "Вдох",
|
||||||
|
"breath_out": "Выдох",
|
||||||
|
"breath_hold": "Пауза",
|
||||||
|
"breath_ready": "Приготовьтесь...",
|
||||||
|
"breath_sit": "Сядьте удобно",
|
||||||
|
"breath_complete": "Сессия завершена! 🌬️",
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
"notifications_empty": "Нет уведомлений",
|
||||||
|
"just_now": "Только что",
|
||||||
|
"ago_m": "м назад",
|
||||||
|
"ago_h": "ч назад"
|
||||||
|
},
|
||||||
|
he: {
|
||||||
|
// Meta
|
||||||
|
"app_title": "MindShift",
|
||||||
|
"app_subtitle": "המאמן האישי שלך ל-CBT",
|
||||||
|
"init_loading": "טוען MindShift...",
|
||||||
|
"init_error": "שגיאה בטעינת האפליקציה",
|
||||||
|
"proactive_badge": "✨ זמן לבדיקת מצב רוח?",
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
"nav_home": "בית",
|
||||||
|
"nav_mood": "מצב רוח",
|
||||||
|
"nav_thoughts": "מחשבות",
|
||||||
|
"nav_gratitude": "הוקרת תודה",
|
||||||
|
"nav_progress": "התקדמות",
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
"auth_welcome": "ברוכים הבאים ל-MindShift",
|
||||||
|
"auth_login": "התחברות",
|
||||||
|
"auth_register": "הרשמה",
|
||||||
|
"auth_name": "שם",
|
||||||
|
"auth_email": "אימייל",
|
||||||
|
"auth_password": "סיסמה",
|
||||||
|
"auth_no_account": "אין לך חשבון?",
|
||||||
|
"auth_has_account": "יש לך כבר חשבון?",
|
||||||
|
"auth_login_failed": "התחברות נכשלה",
|
||||||
|
"auth_reg_failed": "הרשמה נכשלה",
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home_welcome": "ברוכים השבים! 🌟",
|
||||||
|
"home_subtitle": "מוכנים לשינוי מחשבתי?",
|
||||||
|
"home_log_mood": "יומן מצב רוח",
|
||||||
|
"home_record_thought": "יומן מחשבות",
|
||||||
|
"home_gratitude": "הוקרת תודה",
|
||||||
|
"home_daily_vibe": "בדיקה יומית 📊",
|
||||||
|
"home_quick_relief": "הקלה מהירה 🌿",
|
||||||
|
"home_breathe": "נשימה",
|
||||||
|
"home_relax": "הרפיה",
|
||||||
|
"home_stats_mood": "מצב רוח",
|
||||||
|
"home_stats_sessions": "אימונים",
|
||||||
|
"home_stats_avg": "ממוצע שבועי",
|
||||||
|
"home_stats_gratitude": "תודות",
|
||||||
|
"home_retry": "לחץ לנסות שוב",
|
||||||
|
|
||||||
|
// Mood
|
||||||
|
"mood_title": "איך אתם מרגישים?",
|
||||||
|
"mood_joy": "שמחה",
|
||||||
|
"mood_peace": "שלווה",
|
||||||
|
"mood_energy": "אנרגיה",
|
||||||
|
"mood_anxiety": "חרדה",
|
||||||
|
"mood_sadness": "עצב",
|
||||||
|
"mood_anger": "כעס",
|
||||||
|
"mood_intensity": "עוצמה",
|
||||||
|
"mood_notes_placeholder": "מחשבות נוספות?",
|
||||||
|
"mood_save": "שמור מצב רוח",
|
||||||
|
"mood_saved_success": "מצב רוח נשמר בהצלחה!",
|
||||||
|
"mood_select_warning": "אנא בחר מצב רוח",
|
||||||
|
|
||||||
|
// Thoughts
|
||||||
|
"thought_title": "יומן מחשבות 🧠",
|
||||||
|
"thought_situation": "סיטואציה (מי, מה, איפה, מתי?)",
|
||||||
|
"thought_automatic": "מחשבות אוטומטיות (מה עבר בראש?)",
|
||||||
|
"thought_emotions": "רגשות",
|
||||||
|
"thought_add_emotion": "+ הוסף רגש",
|
||||||
|
"thought_evidence": "בעד ונגד",
|
||||||
|
"thought_alternative": "מחשבה אלטרנטיבית",
|
||||||
|
"thought_save": "שמור רשומה",
|
||||||
|
"thought_saved_success": "הרשומה נשמרה בהצלחה!",
|
||||||
|
"thought_fill_warning": "אנא מלא לפחות את הסיטואציה והמחשבות",
|
||||||
|
|
||||||
|
// Gratitude
|
||||||
|
"gratitude_title": "יומן הוקרת תודה 🙏",
|
||||||
|
"gratitude_intro": "3 דברים שאתם מוקירים עליהם תודה היום:",
|
||||||
|
"gratitude_placeholder": "אני מוקיר תודה על...",
|
||||||
|
"gratitude_add": "+ הוסף עוד",
|
||||||
|
"gratitude_save": "שמור",
|
||||||
|
"gratitude_saved_success": "נשמר בהצלחה!",
|
||||||
|
"gratitude_empty_warning": "אנא הוסף לפחות פריט אחד",
|
||||||
|
|
||||||
|
// Progress & History
|
||||||
|
"progress_title": "ההתקדמות שלך 📈",
|
||||||
|
"progress_weekly": "מצב רוח שבועי 📅",
|
||||||
|
"progress_history": "היסטוריה 📜",
|
||||||
|
"history_tab_moods": "מצבי רוח",
|
||||||
|
"history_tab_thoughts": "מחשבות",
|
||||||
|
"history_tab_gratitude": "תודות",
|
||||||
|
"history_empty_mood": "אין עדיין רשומות. התחילו לתעד! 📝",
|
||||||
|
"history_empty_thoughts": "אין עדיין רשומות מחשבה. 🧠",
|
||||||
|
"history_empty_gratitude": "אין עדיין רשומות תודה. 🙏",
|
||||||
|
"history_select_prompt": "בחר קטגוריה לצפייה בהיסטוריה",
|
||||||
|
|
||||||
|
// Quick Actions
|
||||||
|
"quick_title": "פעולות מהירות ⚡",
|
||||||
|
"quick_relax_now": "הרפיה עכשיו",
|
||||||
|
"close": "סגור",
|
||||||
|
|
||||||
|
// Guided Relaxation
|
||||||
|
"guided_title": "אימון קשיבות",
|
||||||
|
"guided_select_mode": "בחר אימון",
|
||||||
|
"mode_grounding": "קרקוע (5-4-3-2-1)",
|
||||||
|
"mode_body_scan": "סריקת גוף",
|
||||||
|
"mode_visualization": "דמיון מודרך",
|
||||||
|
|
||||||
|
"guided_pre_stress": "כמה אתם לחוצים?",
|
||||||
|
"guided_post_stress": "איך אתם מרגישים כעת?",
|
||||||
|
"guided_start": "התחל אימון",
|
||||||
|
"guided_settings": "הגדרות",
|
||||||
|
"guided_ambience": "צלילי רקע",
|
||||||
|
"guided_voice": "קול מנחה",
|
||||||
|
"guided_summary_title": "האימון הושלם",
|
||||||
|
"guided_summary_reduced": "הלחץ ירד ב-",
|
||||||
|
"guided_summary_maintained": "שמרתם על רוגע.",
|
||||||
|
|
||||||
|
"guided_sight_title": "ראייה",
|
||||||
|
"guided_sight_instruction": "הביטו סביבכם.",
|
||||||
|
"guided_sight_sub": "מצאו 5 דברים שאתם רואים.",
|
||||||
|
"guided_touch_title": "מגע",
|
||||||
|
"guided_touch_instruction": "הרגישו מרקמים.",
|
||||||
|
"guided_touch_sub": "מצאו 4 דברים שאפשר לגעת בהם.",
|
||||||
|
"guided_sound_title": "שמיעה",
|
||||||
|
"guided_sound_instruction": "הקשיבו בתשומת לב.",
|
||||||
|
"guided_sound_sub": "זהו 3 צלילים שאתם שומעים.",
|
||||||
|
"guided_smell_title": "ריח",
|
||||||
|
"guided_smell_instruction": "קחו נשימה עמוקה.",
|
||||||
|
"guided_smell_sub": "שימו לב ל-2 ריחות.",
|
||||||
|
"guided_taste_title": "טעם",
|
||||||
|
"guided_taste_instruction": "התמקדו בפה.",
|
||||||
|
"guided_taste_sub": "מצאו דבר 1 שניתן לטעום.",
|
||||||
|
"guided_found_btn": "מצאתי",
|
||||||
|
"guided_complete_title": "כל הכבוד!",
|
||||||
|
"guided_complete_sub": "מרגישים מקורקעים יותר?",
|
||||||
|
"guided_complete_btn": "סיים",
|
||||||
|
|
||||||
|
// Body Scan Script
|
||||||
|
"scan_intro": "מצאו תנוחה נוחה ועצמו עיניים. נתחיל בכמה נשימות עמוקות.",
|
||||||
|
"scan_feet": "התמקדו בכפות הרגליים. הרגישו את המשקל שלהן, חום או קור. תנו להן להתרכך.",
|
||||||
|
"scan_legs": "העבירו את תשומת הלב לשוקיים ולירכיים. אם יש מתח, דמיינו אותו נמס עם הנשיפה.",
|
||||||
|
"scan_stomach": "שימו לב לבטן. הרגישו אותה עולה בעדינות בשאיפה ויורדת בנשיפה. הרפו את שרירי הבטן.",
|
||||||
|
"scan_chest": "העבירו את תשומת הלב לחזה וללב. שימו לב לקצב הנשימה. שחררו את הכתפיים מטה.",
|
||||||
|
"scan_shoulders": "שימו לב לצוואר ולגרון. שחררו כל מתח. אפשרו ללסת להשתחרר מעט.",
|
||||||
|
"scan_face": "הרפו את השרירים סביב העיניים והמצח. תנו לכל הפנים להיות חלקים ורגועים.",
|
||||||
|
"scan_outro": "קחו רגע להרגיש את כל הגוף במצב של רוגע. כשאתם מוכנים, הניעו בעדינות את האצבעות ופקחו עיניים.",
|
||||||
|
|
||||||
|
// Visualization Script
|
||||||
|
"vis_intro": "עצמו עיניים וקחו נשימה עמוקה. אנו יוצאים למסע למקום שליו.",
|
||||||
|
"vis_step1": "דמיינו שאתם עומדים בקצה של יער עתיק ושקט. העצים גבוהים ומגנים. אתם מרגישים בטוחים כאן.",
|
||||||
|
"vis_step2": "כשאתם הולכים עמוק יותר לתוך היער, האוויר נעשה קריר ורענן. אתם יכולים להריח ריח של אורנים ואדמה לחה.",
|
||||||
|
"vis_step3": "אור השמש מסתנן מבעד לחופה מעל, יוצר תבניות של אור על שביל הטחב הרך שמתחת לרגליכם.",
|
||||||
|
"vis_step4": "במרחק, אתם שומעים צליל עדין של פלג מים הזורם על אבנים חלקות. הצליל קצבי ומרגיע.",
|
||||||
|
"vis_outro": "ספגו את השלווה של המקום הזה. דעו שאתם יכולים לחזור לכאן בכל עת. החזירו את המודעות לחדר ופקחו עיניים.",
|
||||||
|
|
||||||
|
// Smart Breathing
|
||||||
|
"breath_balance": "איזון",
|
||||||
|
"breath_relax": "רוגע",
|
||||||
|
"breath_focus": "מיקוד",
|
||||||
|
"breath_in": "שאיפה",
|
||||||
|
"breath_out": "נשיפה",
|
||||||
|
"breath_hold": "החזקה",
|
||||||
|
"breath_ready": "היכונו...",
|
||||||
|
"breath_sit": "שבו בנוחות",
|
||||||
|
"breath_complete": "אימון נשימה הסתיים! 🌬️",
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
"notifications_empty": "אין התראות",
|
||||||
|
"just_now": "כרגע",
|
||||||
|
"ago_m": "לפני דק'",
|
||||||
|
"ago_h": "לפני שע'"
|
||||||
|
}
|
||||||
|
};
|
||||||
41
Test Ideas/NanoJason/nanojason/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
318
Test Ideas/NanoJason/nanojason/README.md
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
# 🚀 NanoJason - Universal AI Prompt Language Translator & Optimizer
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>NanoJason</strong> is a revolutionary, offline-only prompt engineering tool that transforms natural language descriptions into optimized Jason format for maximum AI accuracy across all image generation services.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#features">🌟 Features</a> •
|
||||||
|
<a href="#getting-started">🚀 Getting Started</a> •
|
||||||
|
<a href="#usage">💡 Usage</a> •
|
||||||
|
<a href="#contributing">🤝 Contributing</a> •
|
||||||
|
<a href="#license">📜 License</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌟 **Amazing Features**
|
||||||
|
|
||||||
|
### 🎯 **Three Powerful Modes**
|
||||||
|
|
||||||
|
#### 1. **Jason Translator**
|
||||||
|
- Converts natural language to universal Jason format
|
||||||
|
- Style, mood, character, and item detection
|
||||||
|
- Extracts color palette, composition, and lighting
|
||||||
|
- Perfect for AI image generation compatibility
|
||||||
|
|
||||||
|
#### 2. **NanoPrompt Optimizer**
|
||||||
|
- Deep reverse engineering for maximum AI accuracy
|
||||||
|
- Pattern mapping and keyword enhancement
|
||||||
|
- Quality descriptors and composition optimization
|
||||||
|
- Lighting and color enhancement
|
||||||
|
- Error prevention and artifact reduction
|
||||||
|
- **Accuracy Score:** Up to 95%
|
||||||
|
|
||||||
|
#### 3. **NanoCoder Technical Optimizer**
|
||||||
|
- Specialized for coding and engineering prompts
|
||||||
|
- Technical domain detection (programming, web, mobile, AI, etc.)
|
||||||
|
- Framework and language pattern recognition
|
||||||
|
- Architecture and security optimization
|
||||||
|
- Code context extraction
|
||||||
|
- **Technical Accuracy:** Up to 98%
|
||||||
|
|
||||||
|
### 🔥 **Key Benefits**
|
||||||
|
|
||||||
|
- **🚀 Offline-Only** - No internet connection required
|
||||||
|
- **⚡ Instant Results** - Real-time translation and optimization
|
||||||
|
- **🎨 Universal Compatibility** - Works with all AI image generators
|
||||||
|
- **🔒 Privacy Focused** - No data leaves your device
|
||||||
|
- **📱 Responsive Design** - Perfect for desktop and mobile
|
||||||
|
- **🎯 High Accuracy** - Advanced pattern recognition and optimization
|
||||||
|
- **🔄 Export Options** - Copy, download, and share optimized prompts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **Getting Started**
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js 18+
|
||||||
|
- npm or pnpm
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. **Clone the repository**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/roman-ryzenadvanced/NanoJason.git
|
||||||
|
cd NanoJason
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install dependencies**
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
# or
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run the application**
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Open your browser**
|
||||||
|
```
|
||||||
|
http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 **Usage Examples**
|
||||||
|
|
||||||
|
### **Jason Translator Mode**
|
||||||
|
```text
|
||||||
|
Input: "A magical forest with glowing mushrooms and fairies"
|
||||||
|
Output:
|
||||||
|
{
|
||||||
|
"prompt": "A magical forest with glowing mushrooms and fairies",
|
||||||
|
"style": "fantasy",
|
||||||
|
"mood": "peaceful",
|
||||||
|
"characters": ["fairies"],
|
||||||
|
"items": ["mushrooms", "forest"],
|
||||||
|
"setting": "forest",
|
||||||
|
"action": undefined,
|
||||||
|
"color_palette": ["glowing", "magical"],
|
||||||
|
"composition": undefined,
|
||||||
|
"lighting": "glowing"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### **NanoPrompt Optimizer Mode**
|
||||||
|
```text
|
||||||
|
Input: "A beautiful landscape"
|
||||||
|
Output:
|
||||||
|
- **Original:** "A beautiful landscape"
|
||||||
|
- **Optimized:** "A beautiful landscape, breathtakingly beautiful, ultra-detailed, photorealistic, perfect composition, dramatic lighting, high resolution, no artifacts"
|
||||||
|
- **Accuracy Score:** 85%
|
||||||
|
```
|
||||||
|
|
||||||
|
### **NanoCoder Technical Mode**
|
||||||
|
```text
|
||||||
|
Input: "React web application with TypeScript"
|
||||||
|
Output:
|
||||||
|
- **Original:** "React web application with TypeScript"
|
||||||
|
- **Technical:** "React web application with TypeScript, well-structured and maintainable, clean code, DRY principle, SOLID principles, responsive and accessible web application, technical excellence"
|
||||||
|
- **Technical Accuracy:** 92%
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ **Architecture**
|
||||||
|
|
||||||
|
### **Core Components**
|
||||||
|
|
||||||
|
#### **JasonTranslator** (`src/services/jason-translator.ts`)
|
||||||
|
- Natural language to Jason format conversion
|
||||||
|
- Pattern recognition for style, mood, characters, items
|
||||||
|
- Setting, action, color palette extraction
|
||||||
|
- Composition and lighting detection
|
||||||
|
|
||||||
|
#### **NanoPrompt** (`src/services/nano-prompt.ts`)
|
||||||
|
- Deep reverse engineering optimization
|
||||||
|
- Pattern mapping and keyword enhancement
|
||||||
|
- Quality descriptors and technical specifications
|
||||||
|
- Performance optimization and error prevention
|
||||||
|
|
||||||
|
#### **NanoCoder** (`src/services/nano-coder.ts`)
|
||||||
|
- Technical prompt optimization
|
||||||
|
- Programming language and framework detection
|
||||||
|
- Architecture and security enhancement
|
||||||
|
- Code context extraction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 **UI Components**
|
||||||
|
|
||||||
|
### **Tabbed Interface**
|
||||||
|
- **Jason Translator** - Blue theme for basic translation
|
||||||
|
- **NanoPrompt** - Purple theme for AI optimization
|
||||||
|
- **NanoCoder** - Green theme for technical optimization
|
||||||
|
|
||||||
|
### **Interactive Features**
|
||||||
|
- Quick templates for each mode
|
||||||
|
- Real-time processing with loading indicators
|
||||||
|
- Copy/download functionality for results
|
||||||
|
- Accuracy score display
|
||||||
|
- Performance tips and optimization details
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ **Development**
|
||||||
|
|
||||||
|
### **Project Structure**
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── app/
|
||||||
|
│ ├── page.tsx # Main application page
|
||||||
|
│ ├── layout.tsx # Root layout
|
||||||
|
│ └── globals.css # Global styles
|
||||||
|
├── components/
|
||||||
|
│ ├── layout/
|
||||||
|
│ │ ├── header.tsx # Navigation header
|
||||||
|
│ │ └── footer.tsx # Footer section
|
||||||
|
│ └── ui/
|
||||||
|
│ ├── button.tsx # Button component
|
||||||
|
│ ├── card.tsx # Card component
|
||||||
|
│ └── input.tsx # Input component
|
||||||
|
└── services/
|
||||||
|
├── jason-translator.ts # Jason translation service
|
||||||
|
├── nano-prompt.ts # NanoPrompt optimization
|
||||||
|
├── nano-coder.ts # NanoCoder technical optimization
|
||||||
|
└── api-utils.ts # API utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Technologies Used**
|
||||||
|
- **Next.js** - React framework with App Router
|
||||||
|
- **TypeScript** - Type-safe development
|
||||||
|
- **Tailwind CSS** - Modern utility-first styling
|
||||||
|
- **Lucide React** - Beautiful icon library
|
||||||
|
|
||||||
|
### **Build & Development**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Start production server
|
||||||
|
npm start
|
||||||
|
|
||||||
|
# Linting
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 **Contributing**
|
||||||
|
|
||||||
|
We welcome contributions! Here's how you can help:
|
||||||
|
|
||||||
|
1. **Fork the repository**
|
||||||
|
2. **Create a feature branch** (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. **Make your changes**
|
||||||
|
4. **Commit your changes** (`git commit -m 'Add some amazing feature'`)
|
||||||
|
5. **Push to the branch** (`git push origin feature/amazing-feature`)
|
||||||
|
6. **Open a Pull Request**
|
||||||
|
|
||||||
|
### **Development Guidelines**
|
||||||
|
- Follow TypeScript best practices
|
||||||
|
- Use Tailwind CSS for styling
|
||||||
|
- Write clean, readable code
|
||||||
|
- Add comments for complex logic
|
||||||
|
- Test your changes thoroughly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 **License**
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👨💻 **About the Developer**
|
||||||
|
|
||||||
|
**NanoJason** is developed by **Roman | RyzenAdvanced**
|
||||||
|
|
||||||
|
- **GitHub:** [roman-ryzenadvanced](https://github.com/roman-ryzenadvanced)
|
||||||
|
- **Project Hub:** [Custom Engineered Agents and Tools for Vibe Coders](https://github.com/roman-ryzenadvanced/Custom-Engineered-Agents-and-Tools-for-Vibe-Coders)
|
||||||
|
- **Tools:** [GLM 4.6 Coding Model](https://z.ai/subscribe?ic=R0K78RJKNW)
|
||||||
|
- **Built with:** [TRAE IDE](https://www.trae.ai/s/WJtxyE)
|
||||||
|
|
||||||
|
### **Special Thanks**
|
||||||
|
- **GLM 4.6** - Advanced coding model for AI assistance
|
||||||
|
- **TRAE IDE** - Amazing development environment
|
||||||
|
- **Open Source Community** - For inspiration and collaboration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **Deployment**
|
||||||
|
|
||||||
|
### **Vercel (Recommended)**
|
||||||
|
```bash
|
||||||
|
# Deploy to Vercel
|
||||||
|
npm i -g vercel
|
||||||
|
vercel
|
||||||
|
```
|
||||||
|
|
||||||
|
### **GitHub Pages**
|
||||||
|
```bash
|
||||||
|
# Build and deploy
|
||||||
|
npm run build
|
||||||
|
npm run export
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Other Platforms**
|
||||||
|
- Netlify
|
||||||
|
- Railway
|
||||||
|
- DigitalOcean App Platform
|
||||||
|
- AWS Amplify
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌟 **Show Your Support**
|
||||||
|
|
||||||
|
If you find NanoJason useful, please consider:
|
||||||
|
|
||||||
|
- ⭐ **Star the repository** on GitHub
|
||||||
|
- 🐛 **Report bugs** or suggest features
|
||||||
|
- 📢 **Share** with fellow developers
|
||||||
|
- 💡 **Contribute** to make it even better
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 **Contact**
|
||||||
|
|
||||||
|
- **Issues:** [GitHub Issues](https://github.com/roman-ryzenadvanced/NanoJason/issues)
|
||||||
|
- **Discussions:** [GitHub Discussions](https://github.com/roman-ryzenadvanced/NanoJason/discussions)
|
||||||
|
- **Email:** [Create an issue](https://github.com/roman-ryzenadvanced/NanoJason/issues) for contact
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<strong>Made with ❤️ by Roman | RyzenAdvanced</strong>
|
||||||
|
|
||||||
|
[🚀 Deploy](https://vercel.com/new/clone?repository-url=https://github.com/roman-ryzenadvanced/NanoJason) •
|
||||||
|
[💡 Features](#-amazing-features) •
|
||||||
|
[📖 Documentation](#-getting-started)
|
||||||
|
</div>
|
||||||
18
Test Ideas/NanoJason/nanojason/eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
||||||
|
import nextTs from "eslint-config-next/typescript";
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
...nextVitals,
|
||||||
|
...nextTs,
|
||||||
|
// Override default ignores of eslint-config-next.
|
||||||
|
globalIgnores([
|
||||||
|
// Default ignores of eslint-config-next:
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
19
Test Ideas/NanoJason/nanojason/next.config.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
// Vercel configuration
|
||||||
|
output: 'export',
|
||||||
|
trailingSlash: true,
|
||||||
|
distDir: 'out',
|
||||||
|
images: {
|
||||||
|
unoptimized: true
|
||||||
|
},
|
||||||
|
// Enable React compiler for better performance
|
||||||
|
reactCompiler: true,
|
||||||
|
// Additional optimizations
|
||||||
|
compiler: {
|
||||||
|
removeConsole: process.env.NODE_ENV === 'production',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
6627
Test Ideas/NanoJason/nanojason/package-lock.json
generated
Normal file
32
Test Ideas/NanoJason/nanojason/package.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "nanojason",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
|
"next": "16.1.0",
|
||||||
|
"react": "19.2.3",
|
||||||
|
"react-dom": "19.2.3",
|
||||||
|
"tailwind-merge": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "16.1.0",
|
||||||
|
"tailwindcss": "^4",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Test Ideas/NanoJason/nanojason/postcss.config.mjs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
"@tailwindcss/postcss": {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
1
Test Ideas/NanoJason/nanojason/public/file.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
||||||
|
After Width: | Height: | Size: 391 B |
1
Test Ideas/NanoJason/nanojason/public/globe.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
1
Test Ideas/NanoJason/nanojason/public/next.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
Test Ideas/NanoJason/nanojason/public/vercel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 128 B |
1
Test Ideas/NanoJason/nanojason/public/window.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
||||||
|
After Width: | Height: | Size: 385 B |
@@ -0,0 +1,66 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { code, state } = await request.json()
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Authorization code is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate state for CSRF protection
|
||||||
|
const storedState = request.cookies.get('qwen_auth_state')?.value
|
||||||
|
if (state && storedState !== state) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Invalid state parameter' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In production, exchange code for actual Qwen token
|
||||||
|
// For demo, create a mock token
|
||||||
|
const mockAccessToken = `qwen_token_${code}_${Date.now()}`
|
||||||
|
const mockRefreshToken = `qwen_refresh_${code}_${Date.now()}`
|
||||||
|
const mockExpiresIn = 3600 // 1 hour
|
||||||
|
|
||||||
|
// Store in cookie for server-side access
|
||||||
|
const response = NextResponse.json({
|
||||||
|
access_token: mockAccessToken,
|
||||||
|
refresh_token: mockRefreshToken,
|
||||||
|
expires_in: mockExpiresIn,
|
||||||
|
token_type: 'Bearer',
|
||||||
|
success: true,
|
||||||
|
redirect_to: '/',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Set secure HTTP-only cookie
|
||||||
|
response.cookies.set('qwen_access_token', mockAccessToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: mockExpiresIn,
|
||||||
|
path: '/',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clear state cookie
|
||||||
|
response.cookies.set('qwen_auth_state', '', {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
sameSite: 'lax',
|
||||||
|
maxAge: 0,
|
||||||
|
path: '/',
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Qwen auth callback error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { sanitizeApiKey } from '@/utils/api-utils'
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const authHeader = request.headers.get('authorization')
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Authorization header is required' },
|
||||||
|
{ status: 401 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.substring(7)
|
||||||
|
|
||||||
|
// In a real implementation, you would verify the token with Qwen's API
|
||||||
|
// For now, we'll just check if it exists and is not expired
|
||||||
|
if (!token || token.startsWith('mock_')) {
|
||||||
|
// For demo purposes, accept mock tokens
|
||||||
|
// In production, you would validate this with Qwen's token endpoint
|
||||||
|
return NextResponse.json({
|
||||||
|
valid: true,
|
||||||
|
message: 'Token is valid (demo mode)'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// In production, you would call Qwen's token validation endpoint:
|
||||||
|
/*
|
||||||
|
const validationResponse = await fetch('https://open.bigmodel.cn/api/paas/v4/oauth2/validate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${sanitizeApiKey(token) || token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!validationResponse.ok) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Token is invalid or expired' },
|
||||||
|
{ status: 401 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
valid: true,
|
||||||
|
message: 'Token is valid'
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Token verification error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
|
||||||
|
export interface NarrativeRequest {
|
||||||
|
basePrompt: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
imageCount: number
|
||||||
|
style: string
|
||||||
|
characters?: string
|
||||||
|
items?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NarrativeResponse {
|
||||||
|
prompts: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body: NarrativeRequest = await request.json()
|
||||||
|
|
||||||
|
const {
|
||||||
|
basePrompt,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
imageCount,
|
||||||
|
style,
|
||||||
|
characters = '',
|
||||||
|
items = '',
|
||||||
|
} = body
|
||||||
|
|
||||||
|
if (!basePrompt || !title || !description || imageCount <= 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Missing required fields' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate narrative progression
|
||||||
|
const prompts = generateNarrativeProgression({
|
||||||
|
basePrompt,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
imageCount,
|
||||||
|
style,
|
||||||
|
characters,
|
||||||
|
items,
|
||||||
|
})
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
prompts,
|
||||||
|
success: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Narrative generation error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateNarrativeProgression(config: NarrativeRequest): string[] {
|
||||||
|
const {
|
||||||
|
basePrompt,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
imageCount,
|
||||||
|
style,
|
||||||
|
characters,
|
||||||
|
items,
|
||||||
|
} = config
|
||||||
|
|
||||||
|
const prompts: string[] = []
|
||||||
|
|
||||||
|
// Define narrative structure based on image count
|
||||||
|
const narrativeStructure = getNarrativeStructure(imageCount)
|
||||||
|
|
||||||
|
// Generate prompts for each narrative stage
|
||||||
|
for (let i = 0; i < imageCount; i++) {
|
||||||
|
const stage = narrativeStructure[i] || narrativeStructure[narrativeStructure.length - 1]
|
||||||
|
|
||||||
|
const prompt = generateStagePrompt({
|
||||||
|
stage,
|
||||||
|
basePrompt,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
style,
|
||||||
|
characters,
|
||||||
|
items,
|
||||||
|
index: i,
|
||||||
|
total: imageCount,
|
||||||
|
})
|
||||||
|
|
||||||
|
prompts.push(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompts
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNarrativeStructure(imageCount: number): string[] {
|
||||||
|
if (imageCount <= 3) {
|
||||||
|
return ['introduction', 'development', 'climax']
|
||||||
|
} else if (imageCount <= 5) {
|
||||||
|
return [
|
||||||
|
'introduction',
|
||||||
|
'rising_action',
|
||||||
|
'midpoint',
|
||||||
|
'climax',
|
||||||
|
'resolution'
|
||||||
|
]
|
||||||
|
} else if (imageCount <= 8) {
|
||||||
|
return [
|
||||||
|
'introduction',
|
||||||
|
'setup',
|
||||||
|
'rising_action_1',
|
||||||
|
'rising_action_2',
|
||||||
|
'midpoint',
|
||||||
|
'climax',
|
||||||
|
'falling_action',
|
||||||
|
'resolution'
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
// For longer series, use a more detailed structure
|
||||||
|
const structure = [
|
||||||
|
'introduction',
|
||||||
|
'world_building',
|
||||||
|
'character_introduction',
|
||||||
|
'initial_challenge',
|
||||||
|
'rising_action_1',
|
||||||
|
'rising_action_2',
|
||||||
|
'midpoint_twist',
|
||||||
|
'climax_buildup',
|
||||||
|
'climax',
|
||||||
|
'resolution',
|
||||||
|
'epilogue'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Repeat middle elements for longer series
|
||||||
|
while (structure.length < imageCount) {
|
||||||
|
structure.splice(-2, 0, `development_${structure.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return structure.slice(0, imageCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateStagePrompt(config: {
|
||||||
|
stage: string
|
||||||
|
basePrompt: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
style: string
|
||||||
|
characters: string
|
||||||
|
items: string
|
||||||
|
index: number
|
||||||
|
total: number
|
||||||
|
}): string {
|
||||||
|
const {
|
||||||
|
stage,
|
||||||
|
basePrompt,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
style,
|
||||||
|
characters,
|
||||||
|
items,
|
||||||
|
index,
|
||||||
|
total,
|
||||||
|
} = config
|
||||||
|
|
||||||
|
// Stage-specific templates
|
||||||
|
const stageTemplates: Record<string, string[]> = {
|
||||||
|
introduction: [
|
||||||
|
`Wide establishing shot of ${title} with ${description}. ${characters ? `Featuring ${characters}.` : ''} ${items ? `With ${items} visible.` : ''} ${style} style, atmospheric lighting.`,
|
||||||
|
`Close-up introduction to the main elements of ${title}, showcasing ${description}. ${characters ? `Focus on ${characters}.` : ''} ${style} aesthetic.`,
|
||||||
|
`Panoramic view of the ${title} scene, ${description}. Epic scale with ${characters} ${items ? `and ${items}` : ''} in ${style} style.`
|
||||||
|
],
|
||||||
|
rising_action: [
|
||||||
|
`${title}: Building tension as ${description}. ${characters} ${items ? `interacting with ${items}` : ''}. Dramatic composition in ${style} style.`,
|
||||||
|
`Progressive development of ${title}, ${description}. ${characters} showing character development. ${items} becoming more prominent. ${style} visual style.`,
|
||||||
|
`Action sequence in ${title}, ${description}. ${characters} in motion with ${items}. Dynamic angles and ${style} rendering.`
|
||||||
|
],
|
||||||
|
climax: [
|
||||||
|
`Dramatic climax of ${title}: ${description}. ${characters} at peak action with ${items}. High contrast lighting, ${style} style.`,
|
||||||
|
`Intense climax scene showing ${title}: ${description}. ${characters} in decisive moment with ${items}. Cinematic ${style} composition.`,
|
||||||
|
`Peak action in ${title}: ${description}. ${characters} and ${items} in dramatic confrontation. Epic scale with ${style} aesthetic.`
|
||||||
|
],
|
||||||
|
resolution: [
|
||||||
|
`Resolution of ${title}: ${description}. ${characters} ${items ? `with ${items}` : ''} in peaceful setting. Warm lighting, ${style} style.`,
|
||||||
|
`Conclusion of ${title}: ${description}. ${characters} ${items ? `and ${items}` : ''} in final arrangement. Serene composition, ${style} aesthetic.`,
|
||||||
|
`Final scene of ${title}: ${description}. ${characters} ${items ? `and ${items}` : ''} in satisfying conclusion. ${style} style with atmospheric mood.`
|
||||||
|
],
|
||||||
|
midpoint: [
|
||||||
|
`Midpoint twist in ${title}: ${description}. ${characters} facing unexpected challenge with ${items}. ${style} style with dramatic lighting.`,
|
||||||
|
`Turning point in ${title}: ${description}. ${characters} experiencing major development with ${items}. ${style} visual approach.`
|
||||||
|
],
|
||||||
|
development: [
|
||||||
|
`Development scene in ${title}: ${description}. ${characters} evolving story with ${items}. ${style} style with detailed composition.`,
|
||||||
|
`Progressive scene of ${title}: ${description}. ${characters} advancing narrative with ${items}. ${style} aesthetic with atmospheric elements.`
|
||||||
|
],
|
||||||
|
world_building: [
|
||||||
|
`World-building scene for ${title}: ${description}. Detailed environment showing ${characters} ${items ? `with ${items}` : ''} in ${style} style.`,
|
||||||
|
`Environmental storytelling in ${title}: ${description}. Rich world details featuring ${characters} ${items ? `and ${items}` : ''}. ${style} aesthetic.`
|
||||||
|
],
|
||||||
|
character_introduction: [
|
||||||
|
`Character introduction in ${title}: ${description}. Focus on ${characters} ${items ? `with ${items}` : ''} in ${style} style.`,
|
||||||
|
`Detailed character presentation in ${title}: ${description}. ${characters} ${items ? `interacting with ${items}` : ''}. ${style} visual treatment.`
|
||||||
|
],
|
||||||
|
initial_challenge: [
|
||||||
|
`Initial challenge in ${title}: ${description}. ${characters} facing ${items} in ${style} style.`,
|
||||||
|
`First obstacle scene: ${title} with ${description}. ${characters} encountering ${items}. ${style} composition.`
|
||||||
|
],
|
||||||
|
falling_action: [
|
||||||
|
`Falling action in ${title}: ${description}. ${characters} ${items ? `with ${items}` : ''} in aftermath. ${style} style with reflective mood.`,
|
||||||
|
`Resolution aftermath: ${title} - ${description}. ${characters} ${items ? `and ${items}` : ''} in peaceful setting. ${style} aesthetic.`
|
||||||
|
],
|
||||||
|
epilogue: [
|
||||||
|
`Epilogue scene for ${title}: ${description}. ${characters} ${items ? `with ${items}` : ''} in final moments. ${style} style with nostalgic atmosphere.`,
|
||||||
|
`Closing scene of ${title}: ${description}. ${characters} ${items ? `and ${items}` : ''} in satisfying conclusion. ${style} visual style.`
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default template for unknown stages
|
||||||
|
const defaultTemplates = [
|
||||||
|
`${title}: ${description}. ${characters} ${items ? `with ${items}` : ''} in ${style} style.`,
|
||||||
|
`Scene from ${title}: ${description}. Featuring ${characters} ${items ? `and ${items}` : ''}. ${style} aesthetic.`,
|
||||||
|
`${title} scene: ${description}. ${characters} ${items ? `interacting with ${items}` : ''}. ${style} visual approach.`
|
||||||
|
]
|
||||||
|
|
||||||
|
// Get templates for the current stage
|
||||||
|
const templates = stageTemplates[stage] || defaultTemplates
|
||||||
|
|
||||||
|
// Select template based on index for variety
|
||||||
|
const templateIndex = index % templates.length
|
||||||
|
let prompt = templates[templateIndex]
|
||||||
|
|
||||||
|
// Add progress-specific details
|
||||||
|
const progress = (index + 1) / total
|
||||||
|
if (progress < 0.3) {
|
||||||
|
prompt += ' Beginning of the journey, hopeful atmosphere.'
|
||||||
|
} else if (progress < 0.7) {
|
||||||
|
prompt += ' Mid-journey, building intensity and drama.'
|
||||||
|
} else {
|
||||||
|
prompt += ' Conclusion, satisfying resolution and closure.'
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompt
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
|
import { seriesGenerator } from '@/services/series-generator'
|
||||||
|
|
||||||
|
export interface GenerateSeriesRequest {
|
||||||
|
basePrompt: string
|
||||||
|
config: {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
imageCount: number
|
||||||
|
style: string
|
||||||
|
}
|
||||||
|
characters?: Array<{
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
features: any
|
||||||
|
}>
|
||||||
|
items?: Array<{
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
features: any
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateSeriesResponse {
|
||||||
|
seriesId: string
|
||||||
|
config: any
|
||||||
|
estimatedTime: number
|
||||||
|
status: 'pending' | 'generating' | 'completed' | 'error'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body: GenerateSeriesRequest = await request.json()
|
||||||
|
|
||||||
|
const { basePrompt, config, characters = [], items = [] } = body
|
||||||
|
|
||||||
|
if (!basePrompt || !config || !config.title || !config.description) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Missing required fields: basePrompt, config.title, config.description' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.imageCount < 1 || config.imageCount > 30) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Image count must be between 1 and 30' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the series
|
||||||
|
const series = await seriesGenerator.createSeries(
|
||||||
|
basePrompt,
|
||||||
|
config,
|
||||||
|
characters,
|
||||||
|
items
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simulate image generation (in production, this would trigger actual generation)
|
||||||
|
const estimatedTime = Math.max(30, config.imageCount * 10) // 10-30 seconds per image
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
seriesId: series.id,
|
||||||
|
config: series.config,
|
||||||
|
estimatedTime,
|
||||||
|
status: 'pending',
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Series generation error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to generate series' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
|
const seriesId = searchParams.get('seriesId')
|
||||||
|
|
||||||
|
if (!seriesId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'seriesId parameter is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const series = seriesGenerator.getSeries(seriesId)
|
||||||
|
|
||||||
|
if (!series) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Series not found' },
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate generation status
|
||||||
|
const imageCount = series.images.length
|
||||||
|
const totalImages = series.config.imageCount
|
||||||
|
const progressPercentage = Math.round((imageCount / totalImages) * 100)
|
||||||
|
|
||||||
|
let status: 'pending' | 'generating' | 'completed' | 'error' = 'pending'
|
||||||
|
if (imageCount === 0) {
|
||||||
|
status = 'pending'
|
||||||
|
} else if (imageCount < totalImages) {
|
||||||
|
status = 'generating'
|
||||||
|
} else if (imageCount === totalImages) {
|
||||||
|
status = 'completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
seriesId: series.id,
|
||||||
|
config: series.config,
|
||||||
|
images: series.images,
|
||||||
|
progress: {
|
||||||
|
current: imageCount,
|
||||||
|
total: totalImages,
|
||||||
|
percentage: progressPercentage,
|
||||||
|
},
|
||||||
|
status,
|
||||||
|
createdAt: series.createdAt,
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get series error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url)
|
||||||
|
const seriesId = searchParams.get('seriesId')
|
||||||
|
|
||||||
|
if (!seriesId) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'seriesId parameter is required' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const success = seriesGenerator.deleteSeries(seriesId)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Series not found' },
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Series deleted successfully',
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Delete series error:', error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Internal server error' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
170
Test Ideas/NanoJason/nanojason/src/app/auth/page.tsx
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Sparkles, Loader2, ExternalLink } from "lucide-react"
|
||||||
|
|
||||||
|
export default function AuthPage() {
|
||||||
|
const [isAuthenticating, setIsAuthenticating] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const router = useRouter()
|
||||||
|
const searchParams = useSearchParams()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if we have auth code in URL
|
||||||
|
const code = searchParams.get('code')
|
||||||
|
const error = searchParams.get('error')
|
||||||
|
const success = searchParams.get('success')
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError(`Authentication failed: ${error}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success === 'true') {
|
||||||
|
// Successfully authenticated, redirect to home
|
||||||
|
router.push('/')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
handleAuthCallback(code)
|
||||||
|
}
|
||||||
|
}, [searchParams, router])
|
||||||
|
|
||||||
|
const handleAuthCallback = async (authCode: string) => {
|
||||||
|
setIsAuthenticating(true)
|
||||||
|
setError(null)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Exchange auth code for access token
|
||||||
|
const response = await fetch('/api/auth/qwen/callback', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ code: authCode }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Token exchange failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Store the token (in production, this would be more secure)
|
||||||
|
localStorage.setItem('qwen_access_token', data.access_token)
|
||||||
|
localStorage.setItem('qwen_authenticated', 'true')
|
||||||
|
|
||||||
|
// Redirect to home
|
||||||
|
router.push(data.redirect_to || '/')
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Authentication failed')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Authentication failed')
|
||||||
|
} finally {
|
||||||
|
setIsAuthenticating(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQwenOAuth = () => {
|
||||||
|
// Redirect to Qwen OAuth for actual authentication
|
||||||
|
const clientId = process.env.NEXT_PUBLIC_QWEN_CLIENT_ID || 'demo_client_id'
|
||||||
|
const redirectUri = encodeURIComponent(process.env.NEXT_PUBLIC_QWEN_REDIRECT_URI || 'http://localhost:3000/auth')
|
||||||
|
const scope = 'api_access'
|
||||||
|
const state = Math.random().toString(36).substring(7)
|
||||||
|
|
||||||
|
// Store state for CSRF protection
|
||||||
|
localStorage.setItem('qwen_auth_state', state)
|
||||||
|
|
||||||
|
// Qwen OAuth URL (following Qwen Code implementation)
|
||||||
|
const authUrl = `https://qwen.ai/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&state=${state}`
|
||||||
|
|
||||||
|
window.location.href = authUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDirectLogin = () => {
|
||||||
|
// Direct login for demo - simulates successful OAuth
|
||||||
|
const mockToken = `demo_token_${Date.now()}`
|
||||||
|
localStorage.setItem('qwen_access_token', mockToken)
|
||||||
|
localStorage.setItem('qwen_authenticated', 'true')
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAuthenticating) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<Card className="w-96">
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="text-center space-y-4">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin mx-auto text-blue-600" />
|
||||||
|
<p className="text-gray-600">Authenticating with Qwen...</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center">
|
||||||
|
<Card className="w-96">
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<Sparkles className="h-12 w-12 bg-gradient-to-r from-blue-600 to-purple-600" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-2xl">Welcome to NanoJason</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Connect with Qwen AI to start creating stunning image series
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
{error && (
|
||||||
|
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Button
|
||||||
|
onClick={handleDirectLogin}
|
||||||
|
className="w-full bg-blue-600 text-white hover:bg-blue-700"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
<Sparkles className="mr-2 h-4 w-4" />
|
||||||
|
Quick Demo Login
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<span className="bg-white px-2 text-xs text-gray-500">OR</span>
|
||||||
|
</div>
|
||||||
|
<hr className="border-gray-300" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={handleQwenOAuth}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
<ExternalLink className="mr-2 h-4 w-4" />
|
||||||
|
Connect with Qwen OAuth
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs text-gray-600 text-center space-y-1">
|
||||||
|
<p>By connecting, you agree to Qwen's terms of service</p>
|
||||||
|
<p className="font-medium">🚀 Free tier: 2,000 requests/day</p>
|
||||||
|
<p className="text-green-600">⚡ 60 requests/minute limit</p>
|
||||||
|
<p className="text-blue-600">🔐 OAuth authentication with automatic token refresh</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
BIN
Test Ideas/NanoJason/nanojason/src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
151
Test Ideas/NanoJason/nanojason/src/app/globals.css
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #171717;
|
||||||
|
--card: #ffffff;
|
||||||
|
--card-foreground: #171717;
|
||||||
|
--primary: #0ea5e9;
|
||||||
|
--primary-foreground: #ffffff;
|
||||||
|
--secondary: #f1f5f9;
|
||||||
|
--secondary-foreground: #0f172a;
|
||||||
|
--accent: #d946ef;
|
||||||
|
--accent-foreground: #ffffff;
|
||||||
|
--muted: #f1f5f9;
|
||||||
|
--muted-foreground: #64748b;
|
||||||
|
--border: #e2e8f0;
|
||||||
|
--input: #e2e8f0;
|
||||||
|
--ring: #0ea5e9;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
--card: #0a0a0a;
|
||||||
|
--card-foreground: #ededed;
|
||||||
|
--primary: #0284c7;
|
||||||
|
--primary-foreground: #ffffff;
|
||||||
|
--secondary: #1e293b;
|
||||||
|
--secondary-foreground: #f1f5f9;
|
||||||
|
--accent: #a21caf;
|
||||||
|
--accent-foreground: #ffffff;
|
||||||
|
--muted: #1e293b;
|
||||||
|
--muted-foreground: #94a3b8;
|
||||||
|
--border: #334155;
|
||||||
|
--input: #334155;
|
||||||
|
--ring: #0ea5e9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
--radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
border-color: hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: hsl(var(--background));
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
font-family: "Inter", system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.gradient-bg {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-effect {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 10px 25px -3px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient {
|
||||||
|
background: linear-gradient(to right, hsl(var(--primary)), hsl(var(--accent)));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: hsl(var(--primary));
|
||||||
|
color: hsl(var(--primary-foreground));
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background: hsl(var(--primary) / 0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: hsl(var(--secondary));
|
||||||
|
color: hsl(var(--secondary-foreground));
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
&:hover {
|
||||||
|
background: hsl(var(--secondary) / 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.animate-float {
|
||||||
|
animation: float 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-up {
|
||||||
|
animation: slideUp 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
0% { transform: translateY(10px); opacity: 0; }
|
||||||
|
100% { transform: translateY(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Test Ideas/NanoJason/nanojason/src/app/layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
import { Header } from "@/components/layout/header";
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-sans",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "NanoJason - AI-Powered Image Series Creation",
|
||||||
|
description: "Create stunning image series with consistent characters and narrative using AI. Transform your ideas into visual stories.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={`${inter.variable} font-sans antialiased`}>
|
||||||
|
<Header />
|
||||||
|
<main className="min-h-screen">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import OllamaSettings from "@/components/layout/ollama-settings"
|
||||||
|
|
||||||
|
export default function OllamaSettingsPage() {
|
||||||
|
return <OllamaSettings />
|
||||||
|
}
|
||||||
768
Test Ideas/NanoJason/nanojason/src/app/page.tsx
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Copy, Download, Sparkles, Info, FileText } from "lucide-react"
|
||||||
|
import { jasonTranslator, JasonFormat } from "@/services/jason-translator"
|
||||||
|
import { nanoPrompt, NanoPromptResult } from "@/services/nano-prompt"
|
||||||
|
import { nanoCoder, NanoCoderResult } from "@/services/nano-coder"
|
||||||
|
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
const [inputText, setInputText] = useState('')
|
||||||
|
const [jasonFormat, setJasonFormat] = useState<JasonFormat | null>(null)
|
||||||
|
const [nanoPromptResult, setNanoPromptResult] = useState<NanoPromptResult | null>(null)
|
||||||
|
const [nanoCoderResult, setNanoCoderResult] = useState<NanoCoderResult | null>(null)
|
||||||
|
const [isTranslating, setIsTranslating] = useState(false)
|
||||||
|
const [isOptimizing, setIsOptimizing] = useState(false)
|
||||||
|
const [isCoding, setIsCoding] = useState(false)
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
const [activeTab, setActiveTab] = useState<'jason' | 'nano' | 'code'>('jason')
|
||||||
|
|
||||||
|
const handleTranslate = async () => {
|
||||||
|
if (!inputText.trim()) return
|
||||||
|
|
||||||
|
setIsTranslating(true)
|
||||||
|
try {
|
||||||
|
const result = await jasonTranslator.translateToJason(inputText)
|
||||||
|
setJasonFormat(result)
|
||||||
|
setNanoPromptResult(null) // Clear nano prompt result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Translation failed:', error)
|
||||||
|
} finally {
|
||||||
|
setIsTranslating(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNanoOptimize = async () => {
|
||||||
|
if (!inputText.trim()) return
|
||||||
|
|
||||||
|
setIsOptimizing(true)
|
||||||
|
try {
|
||||||
|
const result = await nanoPrompt.optimizePrompt(inputText)
|
||||||
|
setNanoPromptResult(result)
|
||||||
|
setJasonFormat(null) // Clear jason format result
|
||||||
|
setNanoCoderResult(null) // Clear nano coder result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Nano optimization failed:', error)
|
||||||
|
} finally {
|
||||||
|
setIsOptimizing(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNanoCode = async () => {
|
||||||
|
if (!inputText.trim()) return
|
||||||
|
|
||||||
|
setIsCoding(true)
|
||||||
|
try {
|
||||||
|
const result = await nanoCoder.optimizePrompt(inputText)
|
||||||
|
setNanoCoderResult(result)
|
||||||
|
setJasonFormat(null) // Clear jason format result
|
||||||
|
setNanoPromptResult(null) // Clear nano prompt result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Nano coding failed:', error)
|
||||||
|
} finally {
|
||||||
|
setIsCoding(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
if (!jasonFormat) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(jasonFormat.jason_text)
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to copy:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
if (!jasonFormat) return
|
||||||
|
|
||||||
|
const dataStr = JSON.stringify(jasonFormat, null, 2)
|
||||||
|
const dataBlob = new Blob([dataStr], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(dataBlob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `jason-format-${Date.now()}.json`
|
||||||
|
link.click()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopyNanoPrompt = async () => {
|
||||||
|
if (!nanoPromptResult) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(nanoPromptResult.jason_format)
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to copy:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownloadNanoPrompt = () => {
|
||||||
|
if (!nanoPromptResult) return
|
||||||
|
|
||||||
|
const dataStr = JSON.stringify(nanoPromptResult, null, 2)
|
||||||
|
const dataBlob = new Blob([dataStr], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(dataBlob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `nano-prompt-${Date.now()}.json`
|
||||||
|
link.click()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopyNanoCoder = async () => {
|
||||||
|
if (!nanoCoderResult) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(nanoCoderResult.jason_format)
|
||||||
|
setCopied(true)
|
||||||
|
setTimeout(() => setCopied(false), 2000)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to copy:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownloadNanoCoder = () => {
|
||||||
|
if (!nanoCoderResult) return
|
||||||
|
|
||||||
|
const dataStr = JSON.stringify(nanoCoderResult, null, 2)
|
||||||
|
const dataBlob = new Blob([dataStr], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(dataBlob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `nano-coder-${Date.now()}.json`
|
||||||
|
link.click()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const quickTemplates = [
|
||||||
|
{ text: "A magical forest with glowing mushrooms and fairies", label: "Fantasy Scene" },
|
||||||
|
{ text: "Futuristic city skyline at sunset with flying cars", label: "Sci-Fi City" },
|
||||||
|
{ text: "Cozy cabin in the woods with warm lights", label: "Peaceful Nature" },
|
||||||
|
{ text: "Dragon breathing fire in a medieval castle", label: "Epic Fantasy" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const nanoQuickTemplates = [
|
||||||
|
{ text: "A beautiful portrait of a woman", label: "Portrait" },
|
||||||
|
{ text: "Mountains at sunset", label: "Landscape" },
|
||||||
|
{ text: "A superhero character", label: "Character" },
|
||||||
|
{ text: "A smartphone product", label: "Product" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const nanoCoderQuickTemplates = [
|
||||||
|
{ text: "React web application with TypeScript", label: "Web App" },
|
||||||
|
{ text: "React Native mobile app", label: "Mobile App" },
|
||||||
|
{ text: "Node.js microservice with REST API", label: "Microservice" },
|
||||||
|
{ text: "REST API with authentication", label: "API" },
|
||||||
|
{ text: "Data visualization dashboard", label: "Dashboard" }
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-purple-50">
|
||||||
|
<div className="container mx-auto px-4 py-8 max-w-6xl">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h1 className="text-4xl md:text-6xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent mb-4">
|
||||||
|
NanoJason
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-600 mb-2">
|
||||||
|
Universal AI Prompt Language Translator & Optimizer
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500 max-w-2xl mx-auto">
|
||||||
|
Convert natural language descriptions to Jason format and optimize for maximum AI accuracy
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tabs */}
|
||||||
|
<div className="flex justify-center mb-8">
|
||||||
|
<div className="bg-white rounded-full p-1 shadow-lg border border-gray-200">
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('jason')}
|
||||||
|
className={`px-6 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
||||||
|
activeTab === 'jason'
|
||||||
|
? 'bg-blue-600 text-white shadow-md'
|
||||||
|
: 'text-gray-600 hover:text-blue-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Jason Translator
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('nano')}
|
||||||
|
className={`px-6 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
||||||
|
activeTab === 'nano'
|
||||||
|
? 'bg-purple-600 text-white shadow-md'
|
||||||
|
: 'text-gray-600 hover:text-purple-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
NanoPrompt
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('code')}
|
||||||
|
className={`px-6 py-2 rounded-full text-sm font-medium transition-all duration-200 ${
|
||||||
|
activeTab === 'code'
|
||||||
|
? 'bg-green-600 text-white shadow-md'
|
||||||
|
: 'text-gray-600 hover:text-green-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
NanoCoder
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jason Translator */}
|
||||||
|
{activeTab === 'jason' && (
|
||||||
|
<Card className="mb-12 border-2 border-blue-200 shadow-xl">
|
||||||
|
<CardHeader className="bg-gradient-to-r from-blue-50 to-purple-50">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-2xl">
|
||||||
|
<Sparkles className="h-6 w-6 text-blue-600" />
|
||||||
|
Jason Language Translator
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="text-lg">
|
||||||
|
Describe what you want to create, and I'll convert it to universal Jason format
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 p-6">
|
||||||
|
{/* Quick Templates */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-700 mb-3">Quick Templates:</h4>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||||
|
{quickTemplates.map((template, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setInputText(template.text)}
|
||||||
|
className="text-xs h-auto py-2 px-3 whitespace-normal text-left"
|
||||||
|
>
|
||||||
|
{template.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Describe your image:
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={inputText}
|
||||||
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
|
placeholder="A beautiful landscape with mountains and a lake at sunset..."
|
||||||
|
className="min-h-32 border-2 border-gray-200 focus:border-blue-400 rounded-lg p-3 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Translate Button */}
|
||||||
|
<Button
|
||||||
|
onClick={handleTranslate}
|
||||||
|
disabled={!inputText.trim() || isTranslating}
|
||||||
|
className="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold py-3 text-lg"
|
||||||
|
>
|
||||||
|
{isTranslating ? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||||
|
Translating...
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5" />
|
||||||
|
Translate to Jason Format
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
{jasonFormat && (
|
||||||
|
<div className="space-y-4 mt-6">
|
||||||
|
<div className="bg-gradient-to-r from-green-50 to-blue-50 border-2 border-green-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<h4 className="font-semibold text-green-800 flex items-center gap-2">
|
||||||
|
<FileText className="h-5 w-5" />
|
||||||
|
Jason Format Result
|
||||||
|
</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={handleCopy}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<span className="flex items-center gap-1 text-green-600">
|
||||||
|
<span className="w-3 h-3 bg-green-600 rounded-full"></span>
|
||||||
|
Copied!
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDownload}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
<Download className="h-3 w-3 mr-1" />
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
||||||
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap break-words">
|
||||||
|
{JSON.stringify(jasonFormat, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Format Info */}
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
|
<h5 className="font-semibold text-blue-800 mb-2 flex items-center gap-2">
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
About Jason Format
|
||||||
|
</h5>
|
||||||
|
<p className="text-sm text-blue-700">
|
||||||
|
Jason is a universal prompt language that works with all AI image generators.
|
||||||
|
It includes detailed style, mood, character, and item information for consistent results.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Style Badges */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{jasonFormat.style && (
|
||||||
|
<span className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs">
|
||||||
|
Style: {jasonFormat.style}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{jasonFormat.mood && (
|
||||||
|
<span className="bg-purple-100 text-purple-800 px-2 py-1 rounded text-xs">
|
||||||
|
Mood: {jasonFormat.mood}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{jasonFormat.characters && jasonFormat.characters.length > 0 && (
|
||||||
|
<span className="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">
|
||||||
|
Characters: {jasonFormat.characters.join(', ')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{jasonFormat.items && jasonFormat.items.length > 0 && (
|
||||||
|
<span className="bg-orange-100 text-orange-800 px-2 py-1 rounded text-xs">
|
||||||
|
Items: {jasonFormat.items.join(', ')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* NanoPrompt Optimizer */}
|
||||||
|
{activeTab === 'nano' && (
|
||||||
|
<Card className="mb-12 border-2 border-purple-200 shadow-xl">
|
||||||
|
<CardHeader className="bg-gradient-to-r from-purple-50 to-pink-50">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-2xl">
|
||||||
|
<Sparkles className="h-6 w-6 text-purple-600" />
|
||||||
|
NanoPrompt Optimizer
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="text-lg">
|
||||||
|
Deep reverse engineering for maximum AI accuracy and performance
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 p-6">
|
||||||
|
{/* Quick Templates */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-700 mb-3">Quick Templates:</h4>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||||
|
{nanoQuickTemplates.map((template, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setInputText(template.text)}
|
||||||
|
className="text-xs h-auto py-2 px-3 whitespace-normal text-left"
|
||||||
|
>
|
||||||
|
{template.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Enter your English prompt:
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={inputText}
|
||||||
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
|
placeholder="A beautiful portrait of a woman..."
|
||||||
|
className="min-h-32 border-2 border-gray-200 focus:border-purple-400 rounded-lg p-3 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Optimize Button */}
|
||||||
|
<Button
|
||||||
|
onClick={handleNanoOptimize}
|
||||||
|
disabled={!inputText.trim() || isOptimizing}
|
||||||
|
className="w-full bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white font-semibold py-3 text-lg"
|
||||||
|
>
|
||||||
|
{isOptimizing ? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||||
|
Optimizing...
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5" />
|
||||||
|
Optimize with NanoPrompt
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
{nanoPromptResult && (
|
||||||
|
<div className="space-y-4 mt-6">
|
||||||
|
<div className="bg-gradient-to-r from-purple-50 to-pink-50 border-2 border-purple-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<h4 className="font-semibold text-purple-800 flex items-center gap-2">
|
||||||
|
<FileText className="h-5 w-5" />
|
||||||
|
NanoPrompt Optimization Result
|
||||||
|
</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleCopyNanoPrompt()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<span className="flex items-center gap-1 text-green-600">
|
||||||
|
<span className="w-3 h-3 bg-green-600 rounded-full"></span>
|
||||||
|
Copied!
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleDownloadNanoPrompt()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
<Download className="h-3 w-3 mr-1" />
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Score */}
|
||||||
|
<div className="bg-white rounded-lg p-3 border border-gray-200 mb-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm font-medium text-gray-700">Accuracy Score:</span>
|
||||||
|
<span className="text-lg font-bold text-green-600">{nanoPromptResult.accuracy_score}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Original vs Optimized */}
|
||||||
|
<div className="grid md:grid-cols-2 gap-3 mb-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Original:</div>
|
||||||
|
<div className="bg-gray-50 rounded p-2 text-sm text-gray-800">
|
||||||
|
{nanoPromptResult.original_prompt}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Optimized:</div>
|
||||||
|
<div className="bg-purple-50 rounded p-2 text-sm text-purple-800">
|
||||||
|
{nanoPromptResult.nano_prompt}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Optimizations Applied */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Optimizations Applied:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{nanoPromptResult.optimizations_applied.map((opt, index) => (
|
||||||
|
<span key={index} className="bg-purple-100 text-purple-700 text-xs px-2 py-1 rounded">
|
||||||
|
{opt}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Performance Tips */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Performance Tips:</div>
|
||||||
|
<ul className="text-xs text-gray-700 space-y-1">
|
||||||
|
{nanoPromptResult.performance_tips.map((tip, index) => (
|
||||||
|
<li key={index} className="flex items-start gap-1">
|
||||||
|
<span className="w-1 h-1 bg-purple-600 rounded-full mt-2 flex-shrink-0"></span>
|
||||||
|
{tip}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Jason Format */}
|
||||||
|
<div className="bg-gradient-to-r from-blue-50 to-purple-50 border-2 border-blue-200 rounded-lg p-4">
|
||||||
|
<h5 className="font-semibold text-blue-800 mb-2 flex items-center gap-2">
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
Optimized Jason Format
|
||||||
|
</h5>
|
||||||
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
||||||
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap break-words">
|
||||||
|
{nanoPromptResult.jason_format}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* NanoCoder Optimizer */}
|
||||||
|
{activeTab === 'code' && (
|
||||||
|
<Card className="mb-12 border-2 border-green-200 shadow-xl">
|
||||||
|
<CardHeader className="bg-gradient-to-r from-green-50 to-emerald-50">
|
||||||
|
<CardTitle className="flex items-center gap-2 text-2xl">
|
||||||
|
<Sparkles className="h-6 w-6 text-green-600" />
|
||||||
|
NanoCoder Technical Optimizer
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription className="text-lg">
|
||||||
|
Deep reverse engineering for maximum coding and engineering accuracy
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 p-6">
|
||||||
|
{/* Quick Templates */}
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold text-gray-700 mb-3">Technical Templates:</h4>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
||||||
|
{nanoCoderQuickTemplates.map((template, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setInputText(template.text)}
|
||||||
|
className="text-xs h-auto py-2 px-3 whitespace-normal text-left"
|
||||||
|
>
|
||||||
|
{template.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Enter your technical prompt:
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={inputText}
|
||||||
|
onChange={(e) => setInputText(e.target.value)}
|
||||||
|
placeholder="React web application with TypeScript..."
|
||||||
|
className="min-h-32 border-2 border-gray-200 focus:border-green-400 rounded-lg p-3 w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Optimize Button */}
|
||||||
|
<Button
|
||||||
|
onClick={handleNanoCode}
|
||||||
|
disabled={!inputText.trim() || isCoding}
|
||||||
|
className="w-full bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white font-semibold py-3 text-lg"
|
||||||
|
>
|
||||||
|
{isCoding ? (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin"></div>
|
||||||
|
Optimizing...
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5" />
|
||||||
|
Optimize with NanoCoder
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
{nanoCoderResult && (
|
||||||
|
<div className="space-y-4 mt-6">
|
||||||
|
<div className="bg-gradient-to-r from-green-50 to-emerald-50 border-2 border-green-200 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<h4 className="font-semibold text-green-800 flex items-center gap-2">
|
||||||
|
<FileText className="h-5 w-5" />
|
||||||
|
NanoCoder Technical Result
|
||||||
|
</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => handleCopyNanoCoder()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<span className="flex items-center gap-1 text-green-600">
|
||||||
|
<span className="w-3 h-3 bg-green-600 rounded-full"></span>
|
||||||
|
Copied!
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleDownloadNanoCoder()}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
<Download className="h-3 w-3 mr-1" />
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Score */}
|
||||||
|
<div className="bg-white rounded-lg p-3 border border-gray-200 mb-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-sm font-medium text-gray-700">Technical Accuracy:</span>
|
||||||
|
<span className="text-lg font-bold text-green-600">{nanoCoderResult.accuracy_score}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Original vs Optimized */}
|
||||||
|
<div className="grid md:grid-cols-2 gap-3 mb-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Original:</div>
|
||||||
|
<div className="bg-gray-50 rounded p-2 text-sm text-gray-800">
|
||||||
|
{nanoCoderResult.original_prompt}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Technical:</div>
|
||||||
|
<div className="bg-green-50 rounded p-2 text-sm text-green-800">
|
||||||
|
{nanoCoderResult.nano_coder_prompt}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Technical Specifications */}
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Technical Specifications:</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{nanoCoderResult.technical_specifications.map((spec, index) => (
|
||||||
|
<span key={index} className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded">
|
||||||
|
{spec}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Code Context */}
|
||||||
|
{nanoCoderResult.code_context.length > 0 && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Code Context:</div>
|
||||||
|
<div className="bg-blue-50 rounded p-2 text-xs text-blue-800">
|
||||||
|
{nanoCoderResult.code_context.join(', ')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Performance Tips */}
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-500 font-medium mb-1">Technical Tips:</div>
|
||||||
|
<ul className="text-xs text-gray-700 space-y-1">
|
||||||
|
{nanoCoderResult.performance_tips.map((tip, index) => (
|
||||||
|
<li key={index} className="flex items-start gap-1">
|
||||||
|
<span className="w-1 h-1 bg-green-600 rounded-full mt-2 flex-shrink-0"></span>
|
||||||
|
{tip}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Technical Jason Format */}
|
||||||
|
<div className="bg-gradient-to-r from-blue-50 to-green-50 border-2 border-blue-200 rounded-lg p-4">
|
||||||
|
<h5 className="font-semibold text-blue-800 mb-2 flex items-center gap-2">
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
Technical Jason Format
|
||||||
|
</h5>
|
||||||
|
<div className="bg-white rounded-lg p-3 border border-gray-200">
|
||||||
|
<pre className="text-sm text-gray-800 whitespace-pre-wrap break-words">
|
||||||
|
{nanoCoderResult.jason_format}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Developer Section */}
|
||||||
|
<div className="mt-16 text-center py-8 border-t border-gray-200">
|
||||||
|
<div className="bg-gradient-to-r from-blue-50 to-purple-50 rounded-lg p-6 max-w-4xl mx-auto">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 mb-4">Developed by Roman | RyzenAdvanced</h3>
|
||||||
|
<div className="flex flex-col md:flex-row justify-center items-center gap-6 text-sm text-gray-600">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-medium">GitHub:</span>
|
||||||
|
<a
|
||||||
|
href="https://github.com/roman-ryzenadvanced/Custom-Engineered-Agents-and-Tools-for-Vibe-Coders"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-600 hover:text-blue-800 underline"
|
||||||
|
>
|
||||||
|
Custom Engineered Agents and Tools for Vibe Coders
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-medium">Tools:</span>
|
||||||
|
<a
|
||||||
|
href="https://z.ai/subscribe?ic=R0K78RJKNW"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-green-600 hover:text-green-800 underline"
|
||||||
|
>
|
||||||
|
GLM 4.6 Coding Model
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="font-medium">Built with:</span>
|
||||||
|
<a
|
||||||
|
href="https://www.trae.ai/s/WJtxyE"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-purple-600 hover:text-purple-800 underline"
|
||||||
|
>
|
||||||
|
TRAE IDE
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
90
Test Ideas/NanoJason/nanojason/src/app/test-error/page.tsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { ollamaClient } from '@/services/ollama-client'
|
||||||
|
|
||||||
|
export default function TestErrorPage() {
|
||||||
|
const [testResult, setTestResult] = useState<string>('Click test button to start')
|
||||||
|
const [error, setError] = useState<string>('')
|
||||||
|
|
||||||
|
const testOllamaConnection = async () => {
|
||||||
|
try {
|
||||||
|
setTestResult('Testing API key...')
|
||||||
|
setError('')
|
||||||
|
|
||||||
|
// Test with your actual API key
|
||||||
|
const testKey = '81be8de35b254163a87ba41a5e4e565d.x-TOGbA79gS7AAHTwziOUAIb'
|
||||||
|
|
||||||
|
console.log('🔍 Testing API key:', testKey)
|
||||||
|
console.log('🔍 API key length:', testKey.length)
|
||||||
|
console.log('🔍 API key chars:', testKey.split('').map(c => c.charCodeAt(0)))
|
||||||
|
|
||||||
|
// Set the API key
|
||||||
|
ollamaClient.setApiKey(testKey, false)
|
||||||
|
|
||||||
|
// Test if available
|
||||||
|
const isAvailable = ollamaClient.isAvailable()
|
||||||
|
console.log('🔍 Client available:', isAvailable)
|
||||||
|
|
||||||
|
setTestResult(`API Key Set: ${isAvailable ? '✅' : '❌'}`)
|
||||||
|
|
||||||
|
if (isAvailable) {
|
||||||
|
setTestResult('Testing API call...')
|
||||||
|
|
||||||
|
// Try to list models
|
||||||
|
const models = await ollamaClient.listModels()
|
||||||
|
console.log('✅ Models retrieved:', models)
|
||||||
|
setTestResult(`✅ Success! Found ${models.length} models`)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('❌ Test failed:', err)
|
||||||
|
setError(`Error: ${err.message}`)
|
||||||
|
setTestResult('❌ Test Failed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 p-8">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold mb-6">API Error Debug Test</h1>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow p-6 mb-6">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Test Ollama Connection</h2>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={testOllamaConnection}
|
||||||
|
className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600"
|
||||||
|
>
|
||||||
|
Test API Connection
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="font-semibold">Test Result:</div>
|
||||||
|
<div className="mt-2 p-3 bg-gray-100 rounded">
|
||||||
|
{testResult}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<div className="font-semibold text-red-600">Error:</div>
|
||||||
|
<div className="mt-2 p-3 bg-red-100 rounded text-red-700">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Browser Console</h2>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Check the browser console (F12) for detailed debugging information.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-600 mt-2">
|
||||||
|
Look for 🔍 emoji markers in console logs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,556 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { jasonProcessor, CharacterConsistency, ItemConsistency, SeriesConsistency } from "@/services/jason-processor"
|
||||||
|
import { seriesGenerator } from "@/services/series-generator"
|
||||||
|
import { Users, Package, Palette, Settings, Eye, Edit, Trash2, Plus, CheckCircle, AlertCircle } from "lucide-react"
|
||||||
|
|
||||||
|
interface ConsistencyTrackerProps {
|
||||||
|
seriesId?: string
|
||||||
|
onConsistencyUpdate?: (consistency: SeriesConsistency) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConsistencyTracker({ seriesId, onConsistencyUpdate }: ConsistencyTrackerProps) {
|
||||||
|
const [consistencyProfile, setConsistencyProfile] = useState<SeriesConsistency | null>(null)
|
||||||
|
const [characters, setCharacters] = useState<CharacterConsistency[]>([])
|
||||||
|
const [items, setItems] = useState<ItemConsistency[]>([])
|
||||||
|
const [activeTab, setActiveTab] = useState<'characters' | 'items' | 'colors'>('characters')
|
||||||
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
|
const [editForm, setEditForm] = useState<{
|
||||||
|
type: 'character' | 'item'
|
||||||
|
data: Partial<CharacterConsistency> | Partial<ItemConsistency>
|
||||||
|
index: number
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (seriesId) {
|
||||||
|
loadConsistencyProfile(seriesId)
|
||||||
|
}
|
||||||
|
}, [seriesId])
|
||||||
|
|
||||||
|
const loadConsistencyProfile = (seriesId: string) => {
|
||||||
|
const series = seriesGenerator.getSeries(seriesId)
|
||||||
|
if (series && series.images.length > 0) {
|
||||||
|
const jasonPrompts = series.images.map(image =>
|
||||||
|
jasonProcessor.parseJasonPrompt(image.jasonPrompt || "")
|
||||||
|
).filter(Boolean) as any[]
|
||||||
|
|
||||||
|
if (jasonPrompts.length > 0) {
|
||||||
|
const profile = jasonProcessor.extractConsistencyProfiles(jasonPrompts)
|
||||||
|
setConsistencyProfile(profile)
|
||||||
|
setCharacters(profile.characters)
|
||||||
|
setItems(profile.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addCharacter = () => {
|
||||||
|
const newCharacter: CharacterConsistency = {
|
||||||
|
name: "New Character",
|
||||||
|
description: "Character description",
|
||||||
|
features: {
|
||||||
|
hair: "unknown",
|
||||||
|
eyes: "unknown",
|
||||||
|
clothing: "unknown",
|
||||||
|
accessories: [],
|
||||||
|
expression: "neutral",
|
||||||
|
},
|
||||||
|
consistency: {
|
||||||
|
mustMaintain: ["name", "style"],
|
||||||
|
canVary: ["pose", "expression", "position"],
|
||||||
|
priority: "high",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
setCharacters(prev => [...prev, newCharacter])
|
||||||
|
setEditForm({ type: 'character', data: newCharacter, index: characters.length })
|
||||||
|
setIsEditing(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
const newItem: ItemConsistency = {
|
||||||
|
name: "New Item",
|
||||||
|
description: "Item description",
|
||||||
|
features: {
|
||||||
|
color: "unknown",
|
||||||
|
material: "unknown",
|
||||||
|
size: "unknown",
|
||||||
|
},
|
||||||
|
consistency: {
|
||||||
|
mustMaintain: ["name", "style"],
|
||||||
|
canVary: ["position", "lighting", "size"],
|
||||||
|
priority: "medium",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
setItems(prev => [...prev, newItem])
|
||||||
|
setEditForm({ type: 'item', data: newItem, index: items.length })
|
||||||
|
setIsEditing(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateCharacter = (index: number, updatedCharacter: CharacterConsistency) => {
|
||||||
|
const newCharacters = [...characters]
|
||||||
|
newCharacters[index] = updatedCharacter
|
||||||
|
setCharacters(newCharacters)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateItem = (index: number, updatedItem: ItemConsistency) => {
|
||||||
|
const newItems = [...items]
|
||||||
|
newItems[index] = updatedItem
|
||||||
|
setItems(newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeCharacter = (index: number) => {
|
||||||
|
setCharacters(prev => prev.filter((_, i) => i !== index))
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeItem = (index: number) => {
|
||||||
|
setItems(prev => prev.filter((_, i) => i !== index))
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateConsistencyProfile = () => {
|
||||||
|
if (characters.length > 0 || items.length > 0) {
|
||||||
|
const profile: SeriesConsistency = {
|
||||||
|
characters,
|
||||||
|
items,
|
||||||
|
colorPalette: consistencyProfile?.colorPalette || [],
|
||||||
|
style: consistencyProfile?.style || "realistic",
|
||||||
|
mood: consistencyProfile?.mood || "neutral",
|
||||||
|
narrativeProgression: consistencyProfile?.narrativeProgression || {
|
||||||
|
start: "Beginning",
|
||||||
|
middle: "Middle",
|
||||||
|
end: "End",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
setConsistencyProfile(profile)
|
||||||
|
onConsistencyUpdate?.(profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveEdit = () => {
|
||||||
|
if (!editForm) return
|
||||||
|
|
||||||
|
if (editForm.type === 'character') {
|
||||||
|
updateCharacter(editForm.index, editForm.data as CharacterConsistency)
|
||||||
|
} else {
|
||||||
|
updateItem(editForm.index, editForm.data as ItemConsistency)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEditing(false)
|
||||||
|
setEditForm(null)
|
||||||
|
updateConsistencyProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPriorityColor = (priority: string) => {
|
||||||
|
switch (priority) {
|
||||||
|
case 'high': return 'text-red-600 bg-red-50'
|
||||||
|
case 'medium': return 'text-yellow-600 bg-yellow-50'
|
||||||
|
case 'low': return 'text-green-600 bg-green-50'
|
||||||
|
default: return 'text-gray-600 bg-gray-50'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPriorityIcon = (priority: string) => {
|
||||||
|
switch (priority) {
|
||||||
|
case 'high': return <AlertCircle className="h-3 w-3" />
|
||||||
|
case 'medium': return <AlertCircle className="h-3 w-3" />
|
||||||
|
case 'low': return <CheckCircle className="h-3 w-3" />
|
||||||
|
default: return <CheckCircle className="h-3 w-3" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Overview */}
|
||||||
|
{consistencyProfile && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Settings className="h-5 w-5" />
|
||||||
|
Consistency Overview
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Track character and item consistency across your image series
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid md:grid-cols-4 gap-4">
|
||||||
|
<div className="text-center p-4 bg-muted rounded-lg">
|
||||||
|
<Users className="h-8 w-8 mx-auto mb-2 text-primary" />
|
||||||
|
<div className="text-2xl font-bold">{characters.length}</div>
|
||||||
|
<div className="text-sm text-muted-foreground">Characters</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 bg-muted rounded-lg">
|
||||||
|
<Package className="h-8 w-8 mx-auto mb-2 text-accent" />
|
||||||
|
<div className="text-2xl font-bold">{items.length}</div>
|
||||||
|
<div className="text-sm text-muted-foreground">Items</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 bg-muted rounded-lg">
|
||||||
|
<Palette className="h-8 w-8 mx-auto mb-2 text-green-600" />
|
||||||
|
<div className="text-2xl font-bold">{consistencyProfile.colorPalette.length}</div>
|
||||||
|
<div className="text-sm text-muted-foreground">Colors</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 bg-muted rounded-lg">
|
||||||
|
<Eye className="h-8 w-8 mx-auto mb-2 text-blue-600" />
|
||||||
|
<div className="text-2xl font-bold">
|
||||||
|
{Math.round((characters.length + items.length) * 0.8)}%
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">Consistency</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tab Navigation */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Consistency Management</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex gap-2 mb-6">
|
||||||
|
<Button
|
||||||
|
variant={activeTab === 'characters' ? 'default' : 'outline'}
|
||||||
|
onClick={() => setActiveTab('characters')}
|
||||||
|
>
|
||||||
|
<Users className="mr-2 h-4 w-4" />
|
||||||
|
Characters
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeTab === 'items' ? 'default' : 'outline'}
|
||||||
|
onClick={() => setActiveTab('items')}
|
||||||
|
>
|
||||||
|
<Package className="mr-2 h-4 w-4" />
|
||||||
|
Items
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeTab === 'colors' ? 'default' : 'outline'}
|
||||||
|
onClick={() => setActiveTab('colors')}
|
||||||
|
>
|
||||||
|
<Palette className="mr-2 h-4 w-4" />
|
||||||
|
Colors
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Characters Tab */}
|
||||||
|
{activeTab === 'characters' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h3 className="font-medium">Character Profiles</h3>
|
||||||
|
<Button onClick={addCharacter} size="sm">
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Add Character
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{characters.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
<Users className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||||
|
<p>No characters defined</p>
|
||||||
|
<p className="text-sm">Add characters to maintain consistency</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{characters.map((character, index) => (
|
||||||
|
<Card key={index} className="p-4">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<h4 className="font-medium">{character.name}</h4>
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs ${getPriorityColor(character.consistency.priority)}`}>
|
||||||
|
{getPriorityIcon(character.consistency.priority)}
|
||||||
|
{character.consistency.priority} priority
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
|
{character.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Appearance:</span>
|
||||||
|
<div className="mt-1">
|
||||||
|
<div>Hair: {character.features.hair}</div>
|
||||||
|
<div>Eyes: {character.features.eyes}</div>
|
||||||
|
<div>Clothing: {character.features.clothing}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Consistency:</span>
|
||||||
|
<div className="mt-1">
|
||||||
|
<div>Must maintain: {character.consistency.mustMaintain.join(', ')}</div>
|
||||||
|
<div>Can vary: {character.consistency.canVary.join(', ')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-1 ml-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setEditForm({ type: 'character', data: character, index })}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeCharacter(index)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Items Tab */}
|
||||||
|
{activeTab === 'items' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h3 className="font-medium">Item Profiles</h3>
|
||||||
|
<Button onClick={addItem} size="sm">
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
Add Item
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{items.length === 0 ? (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
<Package className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||||
|
<p>No items defined</p>
|
||||||
|
<p className="text-sm">Add items to maintain consistency</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{items.map((item, index) => (
|
||||||
|
<Card key={index} className="p-4">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<h4 className="font-medium">{item.name}</h4>
|
||||||
|
<span className={`px-2 py-1 rounded-full text-xs ${getPriorityColor(item.consistency.priority)}`}>
|
||||||
|
{getPriorityIcon(item.consistency.priority)}
|
||||||
|
{item.consistency.priority} priority
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground mb-3">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Appearance:</span>
|
||||||
|
<div className="mt-1">
|
||||||
|
<div>Color: {item.features.color}</div>
|
||||||
|
<div>Material: {item.features.material}</div>
|
||||||
|
<div>Size: {item.features.size}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-muted-foreground">Consistency:</span>
|
||||||
|
<div className="mt-1">
|
||||||
|
<div>Must maintain: {item.consistency.mustMaintain.join(', ')}</div>
|
||||||
|
<div>Can vary: {item.consistency.canVary.join(', ')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-1 ml-4">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setEditForm({ type: 'item', data: item, index })}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => removeItem(index)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Colors Tab */}
|
||||||
|
{activeTab === 'colors' && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h3 className="font-medium">Color Palette</h3>
|
||||||
|
|
||||||
|
{consistencyProfile?.colorPalette ? (
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{consistencyProfile.colorPalette.map((color, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="w-12 h-12 rounded-lg border-2 border-gray-200 flex items-center justify-center text-xs font-medium text-white"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
>
|
||||||
|
{color}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
<Palette className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||||
|
<p>No color palette defined</p>
|
||||||
|
<p className="text-sm">Colors will be extracted from your prompts</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Edit Modal */}
|
||||||
|
{isEditing && editForm && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
{editForm.type === 'character' ? 'Edit Character' : 'Edit Item'}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Name</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as any).name || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: { ...editForm.data, name: e.target.value }
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Description</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as any).description || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: { ...editForm.data, description: e.target.value }
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{editForm.type === 'character' && (
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Hair</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as CharacterConsistency).features.hair || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: {
|
||||||
|
...editForm.data,
|
||||||
|
features: {
|
||||||
|
...editForm.data.features,
|
||||||
|
hair: e.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Eyes</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as CharacterConsistency).features.eyes || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: {
|
||||||
|
...editForm.data,
|
||||||
|
features: {
|
||||||
|
...editForm.data.features,
|
||||||
|
eyes: e.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{editForm.type === 'item' && (
|
||||||
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Color</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as ItemConsistency).features.color || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: {
|
||||||
|
...editForm.data,
|
||||||
|
features: {
|
||||||
|
...editForm.data.features,
|
||||||
|
color: e.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Material</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as ItemConsistency).features.material || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: {
|
||||||
|
...editForm.data,
|
||||||
|
features: {
|
||||||
|
...editForm.data.features,
|
||||||
|
material: e.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">Size</label>
|
||||||
|
<Input
|
||||||
|
value={(editForm.data as ItemConsistency).features.size || ''}
|
||||||
|
onChange={(e) => setEditForm({
|
||||||
|
...editForm,
|
||||||
|
data: {
|
||||||
|
...editForm.data,
|
||||||
|
features: {
|
||||||
|
...editForm.data.features,
|
||||||
|
size: e.target.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button onClick={saveEdit}>Save</Button>
|
||||||
|
<Button variant="outline" onClick={() => setIsEditing(false)}>Cancel</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button onClick={updateConsistencyProfile} className="btn-primary">
|
||||||
|
Update Consistency Profile
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
203
Test Ideas/NanoJason/nanojason/src/components/layout/creator.tsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Plus, Trash2, Sparkles, Wand2, Layers } from "lucide-react"
|
||||||
|
|
||||||
|
interface SeriesConfig {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
imageCount: number
|
||||||
|
style: string
|
||||||
|
prompts: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Creator() {
|
||||||
|
const [prompt, setPrompt] = useState("")
|
||||||
|
const [series, setSeries] = useState<SeriesConfig[]>([{
|
||||||
|
id: "1",
|
||||||
|
title: "My First Series",
|
||||||
|
description: "An epic adventure story",
|
||||||
|
imageCount: 5,
|
||||||
|
style: "anime",
|
||||||
|
prompts: []
|
||||||
|
}])
|
||||||
|
|
||||||
|
const addSeries = () => {
|
||||||
|
const newSeries: SeriesConfig = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
title: `Series ${series.length + 1}`,
|
||||||
|
description: "Enter series description",
|
||||||
|
imageCount: 5,
|
||||||
|
style: "realistic",
|
||||||
|
prompts: []
|
||||||
|
}
|
||||||
|
setSeries([...series, newSeries])
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeSeries = (id: string) => {
|
||||||
|
setSeries(series.filter(s => s.id !== id))
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="container mx-auto px-4 py-16">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h2 className="text-3xl font-bold mb-4">Create Your Image Series</h2>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
Describe your vision and configure your story series
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="mb-8">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5" />
|
||||||
|
Image Prompt
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Describe the image you want to create. Be detailed and specific
|
||||||
|
for best results.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Input
|
||||||
|
placeholder="A beautiful magical forest with glowing trees and floating islands..."
|
||||||
|
value={prompt}
|
||||||
|
onChange={(e) => setPrompt(e.target.value)}
|
||||||
|
className="min-h-[100px] resize-none"
|
||||||
|
/>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button className="bg-blue-600 text-white hover:bg-blue-700">
|
||||||
|
<Wand2 className="mr-2 h-4 w-4" />
|
||||||
|
Generate Ideas
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Layers className="h-5 w-5" />
|
||||||
|
Series Configuration
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm" onClick={addSeries}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
Add Series
|
||||||
|
</Button>
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Configure up to 30 images per series with consistent characters
|
||||||
|
and narrative flow.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{series.map((item) => (
|
||||||
|
<div key={item.id} className="border border-gray-200 rounded-lg p-4 space-y-4">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div className="flex-1 space-y-3">
|
||||||
|
<Input
|
||||||
|
placeholder="Series title"
|
||||||
|
value={item.title}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = series.map(s =>
|
||||||
|
s.id === item.id ? {...s, title: e.target.value} : s
|
||||||
|
)
|
||||||
|
setSeries(updated)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="Series description"
|
||||||
|
value={item.description}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = series.map(s =>
|
||||||
|
s.id === item.id ? {...s, description: e.target.value} : s
|
||||||
|
)
|
||||||
|
setSeries(updated)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => removeSeries(item.id)}
|
||||||
|
className="text-red-600 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">
|
||||||
|
Number of Images
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="30"
|
||||||
|
value={item.imageCount}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = series.map(s =>
|
||||||
|
s.id === item.id ? {...s, imageCount: parseInt(e.target.value)} : s
|
||||||
|
)
|
||||||
|
setSeries(updated)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-2 block">
|
||||||
|
Art Style
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className="flex h-10 w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm"
|
||||||
|
value={item.style}
|
||||||
|
onChange={(e) => {
|
||||||
|
const updated = series.map(s =>
|
||||||
|
s.id === item.id ? {...s, style: e.target.value} : s
|
||||||
|
)
|
||||||
|
setSeries(updated)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="realistic">Realistic</option>
|
||||||
|
<option value="anime">Anime</option>
|
||||||
|
<option value="cartoon">Cartoon</option>
|
||||||
|
<option value="fantasy">Fantasy</option>
|
||||||
|
<option value="cyberpunk">Cyberpunk</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-6 border-t border-gray-200">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="text-sm text-gray-600">
|
||||||
|
Total images: {series.reduce((sum, item) => sum + item.imageCount, 0)}
|
||||||
|
</div>
|
||||||
|
<div className="space-x-2">
|
||||||
|
<Button variant="outline">
|
||||||
|
Save Draft
|
||||||
|
</Button>
|
||||||
|
<Button className="bg-blue-600 text-white hover:bg-blue-700">
|
||||||
|
<Sparkles className="mr-2 h-4 w-4" />
|
||||||
|
Generate Series
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { Sparkles, Github, Twitter, Mail } from "lucide-react"
|
||||||
|
|
||||||
|
export function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="border-t bg-white">
|
||||||
|
<div className="container mx-auto px-4 py-12">
|
||||||
|
<div className="grid md:grid-cols-4 gap-8">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Sparkles className="h-6 w-6 bg-gradient-to-r from-blue-600 to-purple-600" />
|
||||||
|
<span className="font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">NanoJason</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
Transform your ideas into stunning visual stories with AI-powered image generation.
|
||||||
|
</p>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<a href="#" className="text-gray-600 hover:text-gray-900">
|
||||||
|
<Github className="h-4 w-4" />
|
||||||
|
<span className="sr-only">GitHub</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-600 hover:text-gray-900">
|
||||||
|
<Twitter className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Twitter</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-600 hover:text-gray-900">
|
||||||
|
<Mail className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Email</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-sm font-semibold">Product</h4>
|
||||||
|
<ul className="space-y-2 text-sm text-gray-600">
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Features</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Gallery</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">API</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Pricing</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-sm font-semibold">Resources</h4>
|
||||||
|
<ul className="space-y-2 text-sm text-gray-600">
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Documentation</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Tutorials</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Blog</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Community</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-sm font-semibold">Company</h4>
|
||||||
|
<ul className="space-y-2 text-sm text-gray-600">
|
||||||
|
<li><a href="#" className="hover:text-gray-900">About</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Contact</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Privacy</a></li>
|
||||||
|
<li><a href="#" className="hover:text-gray-900">Terms</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 pt-8 border-t border-gray-200">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||||
|
<p className="text-sm text-gray-600">
|
||||||
|
© 2024 NanoJason. All rights reserved.
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-600 mt-2 md:mt-0">
|
||||||
|
Powered by Qwen AI and Next.js
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||