├── frontend ├── lib │ ├── models │ │ ├── project.dart │ │ ├── character.dart │ │ ├── location.dart │ │ ├── relationship.dart │ │ ├── event.dart │ │ ├── location_connection.dart │ │ └── event_connection.dart │ ├── utils │ │ ├── constants.dart │ │ ├── text_utils.dart │ │ ├── config_handler.dart │ │ ├── auth.dart │ │ └── theme.dart │ ├── screens │ │ ├── query_screen.dart │ │ ├── knowledge_base_screen.dart │ │ ├── validity_screen.dart │ │ ├── editor_screen.dart │ │ ├── codex_screen.dart │ │ ├── chapters_screen.dart │ │ ├── home_screen.dart │ │ └── landing_screen.dart │ ├── widgets │ │ ├── create_relationship_dialog.dart │ │ ├── edit_relationship_dialog.dart │ │ ├── privacy_policy_dialog.dart │ │ ├── location_dialog.dart │ │ ├── character_relationship_card.dart │ │ └── event_dialog.dart │ └── providers │ │ ├── preset_provider.dart │ │ └── relationship_provider.dart ├── linux │ ├── .gitignore │ ├── main.cc │ ├── flutter │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugins.cmake │ │ ├── generated_plugin_registrant.cc │ │ └── CMakeLists.txt │ ├── my_application.h │ ├── my_application.cc │ └── CMakeLists.txt ├── README.md ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── RunnerTests │ │ └── RunnerTests.swift │ └── .gitignore ├── macos │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Runner │ │ ├── Configs │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ ├── Warnings.xcconfig │ │ │ └── AppInfo.xcconfig │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ ├── app_icon_64.png │ │ │ │ ├── app_icon_1024.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Release.entitlements │ │ ├── DebugProfile.entitlements │ │ ├── MainFlutterWindow.swift │ │ └── Info.plist │ ├── .gitignore │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── RunnerTests │ │ └── RunnerTests.swift ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-128.png │ │ ├── Icon-16.png │ │ ├── Icon-32.png │ │ └── Icon-512.png │ ├── manifest.json │ └── index.html ├── assets │ ├── icon │ │ └── icon.png │ └── images │ │ └── logo.png ├── windows │ ├── runner │ │ ├── resources │ │ │ └── app_icon.ico │ │ ├── resource.h │ │ ├── runner.exe.manifest │ │ ├── utils.h │ │ ├── flutter_window.h │ │ ├── main.cpp │ │ ├── CMakeLists.txt │ │ ├── utils.cpp │ │ ├── flutter_window.cpp │ │ ├── Runner.rc │ │ └── win32_window.h │ ├── .gitignore │ ├── flutter │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugins.cmake │ │ ├── generated_plugin_registrant.cc │ │ └── CMakeLists.txt │ └── CMakeLists.txt ├── android │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── flutterfrontend │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── build.gradle │ └── settings.gradle ├── devtools_options.yaml ├── .gitignore ├── .env.example ├── analysis_options.yaml ├── .metadata └── pubspec.yaml ├── backend ├── requirements.txt ├── .gitattributes ├── .env.example └── api_key_manager.py ├── .gitignore └── changelog.md /frontend/lib/models/project.dart: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Scrollwise 2 | 3 | Frontend for Scrollwise AI 4 | -------------------------------------------------------------------------------- /frontend/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /frontend/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /frontend/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /frontend/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /frontend/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/web/favicon.png -------------------------------------------------------------------------------- /frontend/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/assets/icon/icon.png -------------------------------------------------------------------------------- /frontend/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/assets/images/logo.png -------------------------------------------------------------------------------- /frontend/web/icons/Icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/web/icons/Icon-128.png -------------------------------------------------------------------------------- /frontend/web/icons/Icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/web/icons/Icon-16.png -------------------------------------------------------------------------------- /frontend/web/icons/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/web/icons/Icon-32.png -------------------------------------------------------------------------------- /frontend/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/web/icons/Icon-512.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /frontend/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /frontend/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /frontend/lib/utils/constants.dart: -------------------------------------------------------------------------------- 1 | import 'config_handler.dart'; 2 | 3 | final String apiUrl = 4 | ConfigHandler.get('API_URL', fallback: 'http://localhost:8080'); 5 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /frontend/lib/utils/text_utils.dart: -------------------------------------------------------------------------------- 1 | int getWordCount(String? text) { 2 | if (text == null || text.isEmpty) return 0; 3 | return text.split(RegExp(r'\s+')).where((word) => word.isNotEmpty).length; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/kotlin/com/example/flutterfrontend/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.lotus.scrollwise 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LotusSerene/scrollwise-ai/HEAD/frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /frontend/devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /frontend/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 16 20:32:02 EEST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /frontend/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /frontend/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /frontend/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /frontend/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /frontend/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | python-dotenv 2 | langchain-google-genai 3 | langchain 4 | langchain-core 5 | cachetools 6 | tenacity 7 | langgraph 8 | langchain-community 9 | pydantic 10 | pydantic[email] 11 | fastapi 12 | sqlalchemy 13 | cryptography 14 | aiosqlite 15 | PyPDF2 16 | pdfplumber 17 | python-docx 18 | uvicorn 19 | langchain-qdrant 20 | psutil 21 | qdrant-client 22 | python-multipart 23 | langchain_openai 24 | httpx 25 | tiktoken -------------------------------------------------------------------------------- /frontend/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /backend/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Explicitly declare text files to normalize 5 | *.py text 6 | *.dart text 7 | *.yaml text 8 | *.json text 9 | *.md text 10 | *.txt text 11 | 12 | # Declare files that will always have CRLF line endings on checkout 13 | *.bat text eol=crlf 14 | 15 | # Denote all files that are truly binary and should not be modified 16 | *.png binary 17 | *.jpg binary 18 | *.gif binary -------------------------------------------------------------------------------- /frontend/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /frontend/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/lib/screens/query_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../components/query.dart'; 4 | 5 | class QueryScreen extends StatelessWidget { 6 | final String projectId; 7 | const QueryScreen({Key? key, required this.projectId}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | appBar: AppBar( 13 | title: const Text('Knowledge Base Query'), 14 | ), 15 | body: Query(projectId: projectId), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/lib/screens/knowledge_base_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/knowledge_base.dart'; 3 | 4 | class KnowledgeBaseScreen extends StatelessWidget { 5 | final String projectId; 6 | const KnowledgeBaseScreen({Key? key, required this.projectId}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar(title: const Text('Knowledge Base')), 12 | body: KnowledgeBase(projectId: projectId), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/lib/screens/validity_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/validity.dart'; 3 | 4 | class ValidityScreen extends StatelessWidget { 5 | final String projectId; 6 | const ValidityScreen({Key? key, required this.projectId}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar(title: const Text('Validity')), 12 | body: SafeArea( 13 | child: Validity(projectId: projectId), 14 | ), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/lib/utils/config_handler.dart: -------------------------------------------------------------------------------- 1 | class ConfigHandler { 2 | static final Map _config = { 3 | // Removed Supabase keys (already removed in provided file, ensuring consistency) 4 | 'API_URL': const String.fromEnvironment('API_URL', 5 | defaultValue: 'http://localhost:8080'), // Keep API_URL 6 | }; 7 | 8 | static String get(String key, {String fallback = ''}) { 9 | return _config[key] ?? fallback; 10 | } 11 | 12 | static Map getEnvVars() { 13 | return Map.from(_config); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/lib/screens/editor_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/editor.dart'; 3 | 4 | class EditorScreen extends StatelessWidget { 5 | const EditorScreen({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final String? chapterId = 10 | ModalRoute.of(context)?.settings.arguments as String?; 11 | 12 | return Scaffold( 13 | appBar: AppBar(title: const Text('Editor')), 14 | body: Editor(projectId: '{{PROJECT_ID}}', chapterId: chapterId), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/lib/utils/auth.dart: -------------------------------------------------------------------------------- 1 | // Removed Supabase imports and other unused imports 2 | // Removed main.dart import (navigatorKey not needed here anymore) 3 | // Removed AppState import 4 | // Removed provider import 5 | 6 | // Removed getAuthToken 7 | // Removed getUserId 8 | 9 | // Simplified header function - no auth needed for local app 10 | Future> getAuthHeaders() async { 11 | // No token or session needed 12 | return { 13 | 'Content-Type': 'application/json', 14 | 'Accept': 'application/json', 15 | }; 16 | } 17 | 18 | // Removed isLoggedIn 19 | // Removed signOut 20 | // Removed initializeAuthState 21 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /frontend/lib/screens/codex_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/codex.dart'; 3 | 4 | class CodexScreen extends StatelessWidget { 5 | final String projectId; 6 | 7 | const CodexScreen({Key? key, required this.projectId}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | appBar: AppBar( 13 | title: const Text('Codex'), 14 | leading: IconButton( 15 | icon: const Icon(Icons.arrow_back), 16 | onPressed: () => Navigator.of(context).pop(), 17 | ), 18 | ), 19 | body: Codex(projectId: projectId), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = ScrollWise 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.lotus.scrollwise 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 com.lotus. All rights reserved. 15 | -------------------------------------------------------------------------------- /frontend/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /frontend/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /frontend/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE and editor directories 2 | .vscode/ 3 | 4 | # Python specific 5 | __pycache__/ 6 | .venv/ 7 | backend/__pycache__/ 8 | *.db 9 | backend/.langchain.db 10 | *.pyc 11 | .env 12 | venv/ 13 | .pytest_cache/ 14 | .coverage 15 | htmlcov/ 16 | backend/chroma_db/ 17 | 18 | # Frontend specific gitignore 19 | .dart_tool/ 20 | .flutter-plugins 21 | .flutter-plugins-dependencies 22 | .packages 23 | build/ 24 | .pub/ 25 | .dart_tool/ 26 | *.iml 27 | *.iws 28 | .idea/ 29 | 30 | 31 | # Environment variables 32 | .env 33 | 34 | # Log files 35 | *.log 36 | chrome.log 37 | 38 | # Aider specific 39 | .aider* 40 | 41 | # Chroma specific 42 | chroma_data/* 43 | frontend/chroma_data/ 44 | 45 | # Project specific 46 | chrome_data/ 47 | TODOLIST.md 48 | 49 | # Root level gitignore 50 | .DS_Store 51 | .env 52 | *.log 53 | 54 | # Temp 55 | frontend/build_frontend.bat 56 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /frontend/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | # Frontend Environment Configuration 2 | 3 | # ===================================================== 4 | # Backend API Configuration 5 | # ===================================================== 6 | # Backend server URL - should match the backend server address 7 | API_URL=http://localhost:8080 8 | 9 | # ===================================================== 10 | # Flutter Build Configuration 11 | # ===================================================== 12 | # Note: Flutter uses --dart-define for environment variables 13 | # This .env file is for documentation and can be used by build scripts 14 | # To use these in Flutter, compile with: 15 | # flutter run --dart-define=API_URL=http://localhost:8080 16 | 17 | # ===================================================== 18 | # Development Settings 19 | # ===================================================== 20 | # Set to 'development' for debug mode 21 | FLUTTER_ENV=development -------------------------------------------------------------------------------- /frontend/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_secure_storage_linux 7 | screen_retriever_linux 8 | url_launcher_linux 9 | window_manager 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /frontend/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_secure_storage_windows 7 | protocol_handler 8 | screen_retriever_windows 9 | url_launcher_windows 10 | window_manager 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /frontend/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ScrollWise AI", 3 | "short_name": "ScrollWise", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#ffffff", 7 | "theme_color": "#ffffff", 8 | "description": "Your AI Writing Assistant", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-128.png", 14 | "sizes": "128x128", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-128.png", 24 | "sizes": "128x128", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import flutter_secure_storage_macos 9 | import path_provider_foundation 10 | import protocol_handler 11 | import screen_retriever_macos 12 | import shared_preferences_foundation 13 | import url_launcher_macos 14 | import window_manager 15 | 16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 17 | FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) 18 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 19 | ProtocolHandlerPlugin.register(with: registry.registrar(forPlugin: "ProtocolHandlerPlugin")) 20 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 21 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 22 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 23 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 24 | } 25 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /frontend/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | FlutterSecureStorageWindowsPluginRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); 18 | ProtocolHandlerPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("ProtocolHandlerPlugin")); 20 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 22 | UrlLauncherWindowsRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 24 | WindowManagerPluginRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 26 | } 27 | -------------------------------------------------------------------------------- /frontend/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"ScrollWise", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void fl_register_plugins(FlPluginRegistry* registry) { 15 | g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = 16 | fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); 17 | flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); 18 | g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); 20 | screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); 21 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 23 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 24 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 26 | window_manager_plugin_register_with_registrar(window_manager_registrar); 27 | } 28 | -------------------------------------------------------------------------------- /frontend/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /frontend/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | android { 9 | namespace = "com.lotus.scrollwise" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.lotus.scrollwise" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /frontend/lib/models/character.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Character { 4 | final String id; 5 | final String name; 6 | final String description; 7 | final String backstory; 8 | final DateTime createdAt; 9 | final DateTime updatedAt; 10 | 11 | Character({ 12 | required this.id, 13 | required this.name, 14 | required this.description, 15 | required this.backstory, 16 | required this.createdAt, 17 | required this.updatedAt, 18 | }); 19 | 20 | factory Character.fromJson(Map json) { 21 | return Character( 22 | id: json['id'], 23 | name: json['name'], 24 | description: utf8.decode(json['description'].toString().codeUnits), 25 | backstory: json['backstory'] ?? '', 26 | createdAt: DateTime.parse(json['created_at']), 27 | updatedAt: DateTime.parse(json['updated_at']), 28 | ); 29 | } 30 | 31 | Character copyWith({ 32 | String? id, 33 | String? name, 34 | String? description, 35 | String? backstory, 36 | DateTime? createdAt, 37 | DateTime? updatedAt, 38 | }) { 39 | return Character( 40 | id: id ?? this.id, 41 | name: name ?? this.name, 42 | description: description ?? this.description, 43 | backstory: backstory ?? this.backstory, 44 | createdAt: createdAt ?? this.createdAt, 45 | updatedAt: updatedAt ?? this.updatedAt, 46 | ); 47 | } 48 | 49 | Map toJson() { 50 | return { 51 | 'id': id, 52 | 'name': name, 53 | 'description': description, 54 | 'backstory': backstory, 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/lib/models/location.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Location { 4 | final String id; 5 | final String name; 6 | final String description; 7 | final String? coordinates; 8 | final DateTime createdAt; 9 | final DateTime updatedAt; 10 | final String? significance; 11 | 12 | Location({ 13 | required this.id, 14 | required this.name, 15 | required this.description, 16 | this.coordinates, 17 | required this.createdAt, 18 | required this.updatedAt, 19 | this.significance, 20 | }); 21 | 22 | factory Location.fromJson(Map json) { 23 | return Location( 24 | id: json['id'], 25 | name: json['name'], 26 | description: utf8.decode(json['description'].toString().codeUnits), 27 | coordinates: json['coordinates'], 28 | createdAt: DateTime.parse(json['created_at']), 29 | updatedAt: DateTime.parse(json['updated_at']), 30 | significance: json['significance'], 31 | ); 32 | } 33 | 34 | Map toJson() { 35 | return { 36 | 'id': id, 37 | 'name': name, 38 | 'description': description, 39 | 'coordinates': coordinates, 40 | 'significance': significance, 41 | }; 42 | } 43 | 44 | Location copyWith({ 45 | String? name, 46 | String? description, 47 | String? coordinates, 48 | String? significance, 49 | }) { 50 | return Location( 51 | id: id, 52 | name: name ?? this.name, 53 | description: description ?? this.description, 54 | coordinates: coordinates ?? this.coordinates, 55 | createdAt: createdAt, 56 | updatedAt: DateTime.now().toUtc(), 57 | significance: significance ?? this.significance, 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/lib/models/relationship.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Relationship { 4 | final String id; 5 | final String character1Id; 6 | final String character2Id; 7 | final String character1Name; 8 | final String character2Name; 9 | final String relationshipType; 10 | final String description; 11 | 12 | const Relationship({ 13 | required this.id, 14 | required this.character1Id, 15 | required this.character2Id, 16 | required this.character1Name, 17 | required this.character2Name, 18 | required this.relationshipType, 19 | required this.description, 20 | }); 21 | 22 | factory Relationship.fromJson(Map json) { 23 | return Relationship( 24 | id: json['id'] as String? ?? '', 25 | character1Id: json['character1_id'] as String? ?? '', 26 | character2Id: json['character2_id'] as String? ?? '', 27 | character1Name: json['character1_name'] as String? ?? '', 28 | character2Name: json['character2_name'] as String? ?? '', 29 | relationshipType: json['relationship_type'] as String? ?? '', 30 | description: utf8.decode(json['description'].toString().codeUnits), 31 | ); 32 | } 33 | 34 | Map toJson() => { 35 | 'id': id, 36 | 'character1_id': character1Id, 37 | 'character2_id': character2Id, 38 | 'relationship_type': relationshipType, 39 | 'description': description, 40 | }; 41 | 42 | // Add a method to get the display name if needed 43 | String getDisplayName(String characterId) { 44 | return character1Id == characterId ? character2Id : character1Id; 45 | } 46 | 47 | // Add a method to get the other character's name 48 | String getOtherCharacterName(String characterId) { 49 | return character1Id == characterId ? character2Name : character1Name; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | scrollwise 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | scrollwise 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 17 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 18 | - platform: android 19 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 20 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 21 | - platform: ios 22 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 23 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 24 | - platform: linux 25 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 26 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 27 | - platform: macos 28 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 29 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 30 | - platform: web 31 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 32 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 33 | - platform: windows 34 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 35 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # Backend Environment Configuration 2 | 3 | # ===================================================== 4 | # Server Configuration 5 | # ===================================================== 6 | # CORS Origins (automatically set by ServerManager, but can be overridden) 7 | ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080 8 | 9 | # Log Directory (automatically set by ServerManager) 10 | LOG_DIR=./logs 11 | 12 | # ===================================================== 13 | # OpenRouter Configuration (Optional) 14 | # ===================================================== 15 | # These are used for OpenRouter API headers for better analytics 16 | YOUR_SITE_URL=https://www.op.scrllwise.com/ 17 | YOUR_SITE_NAME=Scrollwise AI 18 | 19 | # ===================================================== 20 | # Database Configuration 21 | # ===================================================== 22 | # SQLite database will be created automatically in the current directory 23 | # No additional database configuration needed for local setup 24 | 25 | # ===================================================== 26 | # Vector Store Configuration 27 | # ===================================================== 28 | # Qdrant vector database will be created locally 29 | # Path: ./qdrant_db (automatically created) 30 | 31 | # ===================================================== 32 | # Cache Configuration 33 | # ===================================================== 34 | # LangChain cache directory (automatically created) 35 | # Path: ./.cache (automatically created) 36 | 37 | # ===================================================== 38 | # Development/Debug Settings 39 | # ===================================================== 40 | # Set to 'development' for debug logging 41 | ENVIRONMENT=production 42 | 43 | # Rate limiting settings (optional overrides) 44 | # GEMINI_PRO_RPM=60 45 | # GEMINI_PRO_TPM=2000000 46 | # GEMINI_FLASH_RPM=60 47 | # GEMINI_FLASH_TPM=2000000 -------------------------------------------------------------------------------- /frontend/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | 42 | # Change the executable name 43 | set(BINARY_NAME "ScrollWise") 44 | -------------------------------------------------------------------------------- /frontend/lib/models/event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class Event { 4 | final String id; 5 | final String title; 6 | final String description; 7 | final DateTime date; 8 | final String? characterId; 9 | final String? locationId; 10 | final DateTime createdAt; 11 | final DateTime updatedAt; 12 | final String? impact; 13 | 14 | Event({ 15 | required this.id, 16 | required this.title, 17 | required this.description, 18 | required this.date, 19 | this.characterId, 20 | this.locationId, 21 | required this.createdAt, 22 | required this.updatedAt, 23 | this.impact, 24 | }); 25 | 26 | factory Event.fromJson(Map json) { 27 | return Event( 28 | id: json['id'], 29 | title: json['title'], 30 | description: utf8.decode(json['description'].toString().codeUnits), 31 | date: DateTime.parse(json['date']), 32 | characterId: json['character_id'], 33 | locationId: json['location_id'], 34 | createdAt: DateTime.parse(json['created_at']), 35 | updatedAt: DateTime.parse(json['updated_at']), 36 | impact: json['impact'], 37 | ); 38 | } 39 | 40 | Map toJson() { 41 | return { 42 | 'id': id, 43 | 'title': title, 44 | 'description': description, 45 | 'date': date.toIso8601String(), 46 | 'character_id': characterId, 47 | 'location_id': locationId, 48 | 'impact': impact, 49 | }; 50 | } 51 | 52 | Event copyWith({ 53 | String? title, 54 | String? description, 55 | DateTime? date, 56 | String? characterId, 57 | String? locationId, 58 | String? impact, 59 | }) { 60 | return Event( 61 | id: id, 62 | title: title ?? this.title, 63 | description: description ?? this.description, 64 | date: date ?? this.date, 65 | characterId: characterId ?? this.characterId, 66 | locationId: locationId ?? this.locationId, 67 | createdAt: createdAt, 68 | updatedAt: DateTime.now().toUtc(), 69 | impact: impact ?? this.impact, 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /frontend/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /frontend/lib/models/location_connection.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class LocationConnection { 4 | final String id; 5 | final String location1Id; 6 | final String location2Id; 7 | final String location1Name; 8 | final String location2Name; 9 | final String travelRoute; 10 | final String culturalExchange; 11 | final DateTime createdAt; 12 | final DateTime updatedAt; 13 | 14 | LocationConnection({ 15 | required this.id, 16 | required this.location1Id, 17 | required this.location2Id, 18 | required this.location1Name, 19 | required this.location2Name, 20 | required this.travelRoute, 21 | required this.culturalExchange, 22 | required this.createdAt, 23 | required this.updatedAt, 24 | }); 25 | 26 | factory LocationConnection.fromJson(Map json) { 27 | String processField(dynamic field) { 28 | if (field == null) return ''; 29 | if (field is List) return field.join(' '); 30 | if (field is Map) return jsonEncode(field); 31 | return field.toString(); 32 | } 33 | 34 | try { 35 | return LocationConnection( 36 | id: json['id']?.toString() ?? '', 37 | location1Id: json['location1_id']?.toString() ?? '', 38 | location2Id: json['location2_id']?.toString() ?? '', 39 | location1Name: json['location1_name']?.toString() ?? '', 40 | location2Name: json['location2_name']?.toString() ?? '', 41 | travelRoute: processField(json['travel_route']), 42 | culturalExchange: processField(json['cultural_exchange']), 43 | createdAt: DateTime.parse( 44 | json['created_at'] ?? DateTime.now().toIso8601String()), 45 | updatedAt: DateTime.parse( 46 | json['updated_at'] ?? DateTime.now().toIso8601String()), 47 | ); 48 | } catch (e) { 49 | rethrow; 50 | } 51 | } 52 | 53 | Map toJson() { 54 | return { 55 | 'id': id, 56 | 'location1_id': location1Id, 57 | 'location2_id': location2Id, 58 | 'travel_route': travelRoute, 59 | 'cultural_exchange': culturalExchange, 60 | }; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /frontend/lib/models/event_connection.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class EventConnection { 4 | final String id; 5 | final String event1Id; 6 | final String event2Id; 7 | final String event1Title; // Add this 8 | final String event2Title; // Add this 9 | final String connectionType; 10 | final String description; 11 | final String impact; 12 | final DateTime createdAt; 13 | final DateTime updatedAt; 14 | 15 | EventConnection({ 16 | required this.id, 17 | required this.event1Id, 18 | required this.event2Id, 19 | required this.event1Title, // Add this 20 | required this.event2Title, // Add this 21 | required this.connectionType, 22 | required this.description, 23 | required this.impact, 24 | required this.createdAt, 25 | required this.updatedAt, 26 | }); 27 | 28 | factory EventConnection.fromJson(Map json) { 29 | String processField(dynamic field) { 30 | if (field == null) return ''; 31 | if (field is List) return field.join(' '); 32 | if (field is Map) return jsonEncode(field); 33 | return field.toString(); 34 | } 35 | 36 | try { 37 | return EventConnection( 38 | id: json['id']?.toString() ?? '', 39 | event1Id: json['event1_id']?.toString() ?? '', 40 | event2Id: json['event2_id']?.toString() ?? '', 41 | event1Title: json['event1_title']?.toString() ?? '', // Add this 42 | event2Title: json['event2_title']?.toString() ?? '', // Add this 43 | connectionType: json['connection_type']?.toString() ?? '', 44 | description: processField(json['description']), 45 | impact: processField(json['impact']), 46 | createdAt: DateTime.parse( 47 | json['created_at'] ?? DateTime.now().toIso8601String()), 48 | updatedAt: DateTime.parse( 49 | json['updated_at'] ?? DateTime.now().toIso8601String()), 50 | ); 51 | } catch (e) { 52 | rethrow; 53 | } 54 | } 55 | 56 | Map toJson() { 57 | return { 58 | 'id': id, 59 | 'event1_id': event1Id, 60 | 'event2_id': event2Id, 61 | 'connection_type': connectionType, 62 | 'description': description, 63 | 'impact': impact, 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /frontend/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ScrollWise AI 23 | 24 | 25 | 29 | 30 | 31 | 32 | 59 | 60 | 61 |
62 |
63 |
64 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /frontend/lib/screens/chapters_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import '../providers/app_state.dart'; 4 | 5 | class ChaptersScreen extends StatefulWidget { 6 | final String projectId; 7 | 8 | const ChaptersScreen({Key? key, required this.projectId}) : super(key: key); 9 | 10 | @override 11 | State createState() => _ChaptersScreenState(); 12 | } 13 | 14 | class _ChaptersScreenState extends State { 15 | bool _isLoading = true; 16 | String _error = ''; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _fetchChapters(); 22 | } 23 | 24 | Future _fetchChapters() async { 25 | setState(() { 26 | _isLoading = true; 27 | _error = ''; 28 | }); 29 | 30 | try { 31 | await Provider.of(context, listen: false) 32 | .fetchChapters(widget.projectId); 33 | 34 | if (mounted) { 35 | setState(() { 36 | _isLoading = false; 37 | }); 38 | } 39 | } catch (error) { 40 | if (mounted) { 41 | setState(() { 42 | _error = 'Error fetching chapters: $error'; 43 | _isLoading = false; 44 | }); 45 | } 46 | } 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | final appState = Provider.of(context); 52 | final chapters = appState.chapters; 53 | 54 | if (_isLoading) { 55 | return const Center(child: CircularProgressIndicator()); 56 | } 57 | 58 | if (_error.isNotEmpty) { 59 | return Center( 60 | child: Column( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | children: [ 63 | Text(_error, style: const TextStyle(color: Colors.red)), 64 | ElevatedButton( 65 | onPressed: _fetchChapters, 66 | child: const Text('Retry'), 67 | ), 68 | ], 69 | ), 70 | ); 71 | } 72 | 73 | return Scaffold( 74 | appBar: AppBar( 75 | title: const Text('Chapters'), 76 | automaticallyImplyLeading: false, 77 | ), 78 | body: RefreshIndicator( 79 | onRefresh: _fetchChapters, 80 | child: ListView.builder( 81 | itemCount: chapters.length, 82 | itemBuilder: (context, index) { 83 | final chapter = chapters[index]; 84 | return ListTile( 85 | title: Text(chapter['title']), 86 | onTap: () { 87 | Navigator.pushNamed(context, '/editor', 88 | arguments: chapter['id']); 89 | }, 90 | ); 91 | }, 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /frontend/lib/widgets/create_relationship_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CreateRelationshipDialog extends StatefulWidget { 4 | final List> characters; 5 | final Function(String, String, String) onCreateRelationship; 6 | 7 | const CreateRelationshipDialog({ 8 | Key? key, 9 | required this.characters, 10 | required this.onCreateRelationship, 11 | }) : super(key: key); 12 | 13 | @override 14 | State createState() => 15 | _CreateRelationshipDialogState(); 16 | } 17 | 18 | class _CreateRelationshipDialogState extends State { 19 | String? _character1; 20 | String? _character2; 21 | String _relationshipType = ''; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return AlertDialog( 26 | title: const Text('Create Relationship'), 27 | content: Column( 28 | mainAxisSize: MainAxisSize.min, 29 | children: [ 30 | DropdownButtonFormField( 31 | value: _character1, 32 | items: widget.characters 33 | .map((c) => DropdownMenuItem( 34 | value: c['id'].toString(), 35 | child: Text(c['name'].toString()))) 36 | .toList(), 37 | onChanged: (value) => setState(() => _character1 = value), 38 | decoration: const InputDecoration(labelText: 'Character 1'), 39 | ), 40 | DropdownButtonFormField( 41 | value: _character2, 42 | items: widget.characters 43 | .map((c) => DropdownMenuItem( 44 | value: c['id'].toString(), 45 | child: Text(c['name'].toString()))) 46 | .toList(), 47 | onChanged: (value) => setState(() => _character2 = value), 48 | decoration: const InputDecoration(labelText: 'Character 2'), 49 | ), 50 | TextField( 51 | onChanged: (value) => setState(() => _relationshipType = value), 52 | decoration: const InputDecoration(labelText: 'Relationship Type'), 53 | ), 54 | ], 55 | ), 56 | actions: [ 57 | TextButton( 58 | child: const Text('Cancel'), 59 | onPressed: () => Navigator.of(context).pop(), 60 | ), 61 | ElevatedButton( 62 | onPressed: _character1 != null && 63 | _character2 != null && 64 | _relationshipType.isNotEmpty 65 | ? () { 66 | widget.onCreateRelationship( 67 | _character1!, _character2!, _relationshipType); 68 | Navigator.of(context).pop(); 69 | } 70 | : null, 71 | child: const Text('Create'), 72 | ), 73 | ], 74 | ); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /frontend/lib/widgets/edit_relationship_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/relationship.dart'; 3 | 4 | class EditRelationshipDialog extends StatefulWidget { 5 | final Relationship relationship; 6 | final Function(Relationship) onEditRelationship; 7 | 8 | const EditRelationshipDialog({ 9 | Key? key, 10 | required this.relationship, 11 | required this.onEditRelationship, 12 | }) : super(key: key); 13 | 14 | @override 15 | EditRelationshipDialogState createState() => EditRelationshipDialogState(); 16 | } 17 | 18 | class EditRelationshipDialogState extends State { 19 | late TextEditingController _relationshipTypeController; 20 | late TextEditingController _descriptionController; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _relationshipTypeController = 26 | TextEditingController(text: widget.relationship.relationshipType); 27 | _descriptionController = 28 | TextEditingController(text: widget.relationship.description); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | _relationshipTypeController.dispose(); 34 | _descriptionController.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return AlertDialog( 41 | title: const Text('Edit Relationship'), 42 | content: Column( 43 | mainAxisSize: MainAxisSize.min, 44 | children: [ 45 | Text( 46 | 'Between: ${widget.relationship.character1Name} and ${widget.relationship.character2Name}'), 47 | const SizedBox(height: 16), 48 | TextField( 49 | controller: _relationshipTypeController, 50 | decoration: const InputDecoration(labelText: 'Relationship Type'), 51 | ), 52 | TextField( 53 | controller: _descriptionController, 54 | decoration: const InputDecoration(labelText: 'Description'), 55 | maxLines: 3, 56 | ), 57 | ], 58 | ), 59 | actions: [ 60 | TextButton( 61 | child: const Text('Cancel'), 62 | onPressed: () => Navigator.of(context).pop(), 63 | ), 64 | ElevatedButton( 65 | child: const Text('Save'), 66 | onPressed: () { 67 | final updatedRelationship = Relationship( 68 | id: widget.relationship.id, 69 | character1Id: widget.relationship.character1Id, 70 | character2Id: widget.relationship.character2Id, 71 | character1Name: widget.relationship.character1Name, 72 | character2Name: widget.relationship.character2Name, 73 | relationshipType: _relationshipTypeController.text, 74 | description: _descriptionController.text, 75 | ); 76 | widget.onEditRelationship(updatedRelationship); 77 | Navigator.of(context).pop(); 78 | }, 79 | ), 80 | ], 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /frontend/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /frontend/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /frontend/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.6" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "Lotus" "\0" 93 | VALUE "FileDescription", "ScrollWise AI - Your AI Writing Assistant" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "ScrollWise" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2024 Lotus. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "ScrollWise.exe" "\0" 98 | VALUE "ProductName", "ScrollWise" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /frontend/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /frontend/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /frontend/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /frontend/lib/widgets/privacy_policy_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PrivacyPolicyDialog extends StatelessWidget { 4 | const PrivacyPolicyDialog({super.key}); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return AlertDialog( 9 | title: const Text('Privacy Policy'), 10 | content: SingleChildScrollView( 11 | child: Column( 12 | crossAxisAlignment: CrossAxisAlignment.start, 13 | children: [ 14 | const Text( 15 | 'Last updated: 05.03.2025', 16 | style: TextStyle(fontStyle: FontStyle.italic), 17 | ), 18 | const SizedBox(height: 16), 19 | const Text( 20 | 'At ScrollWise AI, we prioritize the protection of your privacy and personal information. This Privacy Policy outlines how we handle your data when using our application, which is designed with a local-first approach.', 21 | ), 22 | const SizedBox(height: 16), 23 | _buildSection( 24 | 'Local-First Application', 25 | 'ScrollWise AI operates primarily on your local device. This means your writing content and project data are stored locally on your device, giving you more control over your information.', 26 | ), 27 | _buildSection( 28 | 'API Key Handling', 29 | 'To utilize the AI features, you need to provide your own API key. This API key is stored securely on your local device and is used to directly access the Gemini API. We do not store or have access to your API key.', 30 | ), 31 | _buildSection( 32 | 'Information We Collect', 33 | 'We collect and process only the following types of information:\n' 34 | '• Account information (email address, username)', 35 | ), 36 | _buildSection( 37 | 'How We Use Your Information', 38 | 'Your account information is used to:\n' 39 | '• Authenticate and manage your account\n' 40 | '• Personalize your experience', 41 | ), 42 | _buildSection( 43 | 'Data Storage and Security', 44 | 'Your account information is stored securely. Your writing content and projects are stored locally on your device.', 45 | ), 46 | _buildSection( 47 | 'AI Processing', 48 | 'When you use AI features, your writing content is processed by the Gemini API directly using your API key. We do not process or store your writing content on our servers.', 49 | ), 50 | _buildSection( 51 | 'Your Rights', 52 | 'You have the right to:\n' 53 | '• Access your account data\n' 54 | '• Request data correction or deletion\n' 55 | '• Opt-out of certain data processing\n' 56 | '• Withdraw consent at any time', 57 | ), 58 | _buildSection( 59 | 'Changes to Policy', 60 | 'We may update this Privacy Policy as our services evolve. Significant changes will be notified through the application, and continued use after changes constitutes acceptance.', 61 | ), 62 | _buildSection( 63 | 'Contact Us', 64 | 'For privacy-related inquiries, please join our Discord server.', 65 | ), 66 | ], 67 | ), 68 | ), 69 | actions: [ 70 | TextButton( 71 | child: const Text('Close'), 72 | onPressed: () => Navigator.of(context).pop(), 73 | ), 74 | ], 75 | ); 76 | } 77 | 78 | Widget _buildSection(String title, String content) { 79 | return Column( 80 | crossAxisAlignment: CrossAxisAlignment.start, 81 | children: [ 82 | const SizedBox(height: 16), 83 | Text( 84 | title, 85 | style: const TextStyle(fontWeight: FontWeight.bold), 86 | ), 87 | const SizedBox(height: 8), 88 | Text(content), 89 | ], 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /frontend/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /frontend/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(ScrollWise LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "ScrollWise") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /frontend/lib/utils/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppTheme { 4 | // Color constants 5 | static const Color _primaryDark = Color(0xFF1A1A1A); // Lighter background 6 | static const Color _secondaryDark = Color(0xFF242424); // Lighter secondary 7 | static const Color _primaryText = Color(0xFFFAFAFA); // Crisp white text 8 | static const Color _secondaryText = Color(0xFFBBBBBB); // Lighter gray text 9 | static const Color _accentBlue = Color(0xFF60A5FA); // Keep modern blue accent 10 | static const Color _accentTeal = Color(0xFF2DD4BF); // Keep fresh teal accent 11 | static const Color _borderColor = Color(0xFF323232); // Lighter border 12 | static const Color _errorColor = Color(0xFFEF4444); // Keep modern red 13 | static const Color _warningColor = Color(0xFFFBBF24); // Keep modern yellow 14 | static const Color _surfaceColor = Color(0xFF202020); // Lighter surface color 15 | // Text Styles 16 | static const TextTheme _textTheme = TextTheme( 17 | displayLarge: TextStyle( 18 | fontSize: 32, 19 | fontWeight: FontWeight.bold, 20 | color: _primaryText, 21 | letterSpacing: -1.0, 22 | ), 23 | displayMedium: TextStyle( 24 | fontSize: 28, 25 | fontWeight: FontWeight.bold, 26 | color: _primaryText, 27 | letterSpacing: -0.5, 28 | ), 29 | bodyLarge: TextStyle( 30 | fontSize: 16, 31 | color: _primaryText, 32 | letterSpacing: 0.1, 33 | ), 34 | bodyMedium: TextStyle( 35 | fontSize: 14, 36 | color: _secondaryText, 37 | letterSpacing: 0.1, 38 | ), 39 | labelLarge: TextStyle( 40 | fontSize: 14, 41 | fontWeight: FontWeight.w500, 42 | color: _primaryText, 43 | letterSpacing: 0.1, 44 | ), 45 | ); 46 | 47 | static final ThemeData darkTheme = ThemeData( 48 | useMaterial3: true, 49 | brightness: Brightness.dark, 50 | primaryColor: _accentBlue, 51 | scaffoldBackgroundColor: _primaryDark, 52 | textTheme: _textTheme, 53 | colorScheme: const ColorScheme.dark( 54 | primary: _accentBlue, 55 | secondary: _accentTeal, 56 | surface: _surfaceColor, 57 | error: _errorColor, 58 | secondaryContainer: _warningColor, 59 | ), 60 | 61 | // Card Theme 62 | cardTheme: CardTheme( 63 | color: _surfaceColor, 64 | elevation: 0, 65 | shape: RoundedRectangleBorder( 66 | borderRadius: BorderRadius.circular(16), 67 | side: BorderSide(color: _borderColor.withOpacity(0.5), width: 1), 68 | ), 69 | ), 70 | 71 | // AppBar Theme 72 | appBarTheme: const AppBarTheme( 73 | backgroundColor: _primaryDark, 74 | elevation: 0, 75 | centerTitle: true, 76 | titleTextStyle: TextStyle( 77 | color: _primaryText, 78 | fontSize: 20, 79 | fontWeight: FontWeight.w600, 80 | letterSpacing: -0.5, 81 | ), 82 | ), 83 | 84 | // Button Themes 85 | elevatedButtonTheme: ElevatedButtonThemeData( 86 | style: ElevatedButton.styleFrom( 87 | backgroundColor: _accentBlue, 88 | foregroundColor: _primaryText, 89 | padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), 90 | elevation: 0, 91 | shape: RoundedRectangleBorder( 92 | borderRadius: BorderRadius.circular(12), 93 | ), 94 | ), 95 | ), 96 | 97 | // Input Decoration 98 | inputDecorationTheme: InputDecorationTheme( 99 | filled: true, 100 | fillColor: _secondaryDark, 101 | contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), 102 | border: OutlineInputBorder( 103 | borderRadius: BorderRadius.circular(12), 104 | borderSide: BorderSide.none, 105 | ), 106 | enabledBorder: OutlineInputBorder( 107 | borderRadius: BorderRadius.circular(12), 108 | borderSide: BorderSide(color: _borderColor.withOpacity(0.3)), 109 | ), 110 | focusedBorder: OutlineInputBorder( 111 | borderRadius: BorderRadius.circular(12), 112 | borderSide: const BorderSide(color: _accentBlue, width: 2), 113 | ), 114 | labelStyle: TextStyle(color: _primaryText.withOpacity(0.8)), 115 | hintStyle: TextStyle(color: _secondaryText.withOpacity(0.6)), 116 | ), 117 | 118 | // Divider Theme 119 | dividerTheme: DividerThemeData( 120 | color: _borderColor.withOpacity(0.3), 121 | thickness: 1, 122 | ), 123 | 124 | // Icon Theme 125 | iconTheme: IconThemeData( 126 | color: _primaryText.withOpacity(0.8), 127 | size: 24, 128 | ), 129 | ); 130 | 131 | // You can add a light theme here if needed 132 | static final ThemeData lightTheme = ThemeData.light(); 133 | } 134 | -------------------------------------------------------------------------------- /frontend/lib/providers/preset_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:convert'; 4 | import '../utils/constants.dart'; 5 | 6 | import 'package:logging/logging.dart'; 7 | 8 | final _logger = Logger('PresetProvider'); 9 | 10 | class PresetProvider with ChangeNotifier { 11 | List _presets = []; 12 | String? _selectedPreset; 13 | Map? _currentPreset; 14 | bool _isLoading = false; 15 | String? _currentProjectId; 16 | 17 | List get presets => _presets; 18 | String? get selectedPreset => _selectedPreset; 19 | Map? get currentPreset => _currentPreset; 20 | bool get isLoading => _isLoading; 21 | 22 | void setProjectId(String projectId) { 23 | _currentProjectId = projectId; 24 | } 25 | 26 | Future fetchPresets() async { 27 | if (_currentProjectId == null) { 28 | throw Exception('No project ID set'); 29 | } 30 | 31 | _isLoading = true; 32 | notifyListeners(); 33 | 34 | try { 35 | final response = await http.get( 36 | Uri.parse('$apiUrl/projects/$_currentProjectId/presets'), 37 | ); 38 | 39 | if (response.statusCode == 200) { 40 | final Map data = json.decode(response.body); 41 | final List presetList = data['presets']; 42 | _presets = 43 | presetList.map((preset) => preset['name'] as String).toList(); 44 | notifyListeners(); 45 | } else { 46 | throw Exception('Failed to load presets: ${response.statusCode}'); 47 | } 48 | } catch (error) { 49 | _logger.severe('Error fetching presets: $error'); 50 | rethrow; 51 | } finally { 52 | _isLoading = false; 53 | notifyListeners(); 54 | } 55 | } 56 | 57 | Future loadPreset(String presetName) async { 58 | if (presetName == "select a preset") { 59 | _selectedPreset = null; 60 | _currentPreset = null; 61 | notifyListeners(); 62 | return; 63 | } 64 | try { 65 | final response = await http.get( 66 | Uri.parse( 67 | '$apiUrl/projects/$_currentProjectId/presets/${Uri.encodeComponent(presetName)}'), 68 | ); 69 | 70 | if (response.statusCode == 200) { 71 | final preset = json.decode(response.body); 72 | _selectedPreset = presetName; 73 | _currentPreset = preset['data']; 74 | notifyListeners(); 75 | } else { 76 | throw Exception('Failed to load preset: ${response.statusCode}'); 77 | } 78 | } catch (error) { 79 | _logger.severe('Error loading preset: $error'); 80 | rethrow; 81 | } 82 | } 83 | 84 | Future savePreset( 85 | String presetName, Map presetData) async { 86 | try { 87 | if (_currentProjectId == null) { 88 | throw Exception('No project ID set'); 89 | } 90 | 91 | final response = await http.post( 92 | Uri.parse('$apiUrl/projects/$_currentProjectId/presets'), 93 | headers: { 94 | 'Content-Type': 'application/json', 95 | }, 96 | body: json.encode({ 97 | 'name': presetName, 98 | 'data': presetData, 99 | 'project_id': _currentProjectId, 100 | }), 101 | ); 102 | 103 | if (response.statusCode == 200) { 104 | await fetchPresets(); 105 | } else { 106 | throw Exception( 107 | 'Failed to save preset: ${response.statusCode} - ${response.body}'); 108 | } 109 | } catch (error) { 110 | _logger.severe('Error saving preset: $error'); 111 | rethrow; 112 | } 113 | } 114 | 115 | Future deletePreset(String presetName) async { 116 | try { 117 | if (_currentProjectId == null) { 118 | throw Exception('No project ID set'); 119 | } 120 | 121 | final response = await http.delete( 122 | Uri.parse( 123 | '$apiUrl/projects/$_currentProjectId/presets/${Uri.encodeComponent(presetName)}'), 124 | ); 125 | 126 | if (response.statusCode == 200) { 127 | _presets.remove(presetName); 128 | if (_selectedPreset == presetName) { 129 | _selectedPreset = null; 130 | _currentPreset = null; 131 | } 132 | await fetchPresets(); // Refresh the list after deletion 133 | notifyListeners(); 134 | } else { 135 | throw Exception( 136 | 'Failed to delete preset: ${response.statusCode} - ${response.body}'); 137 | } 138 | } catch (error) { 139 | _logger.severe('Error deleting preset: $error'); 140 | rethrow; 141 | } 142 | } 143 | 144 | Map get defaultPreset => { 145 | 'numChapters': 1, 146 | 'plot': '', 147 | 'writingStyle': '', 148 | 'styleGuide': '', 149 | 'wordCount': 1000, 150 | 'additionalInstructions': '', 151 | }; 152 | } 153 | -------------------------------------------------------------------------------- /frontend/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "ScrollWise AI"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "ScrollWise AI"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | return MY_APPLICATION(g_object_new(my_application_get_type(), 121 | "application-id", APPLICATION_ID, 122 | "flags", G_APPLICATION_NON_UNIQUE, 123 | nullptr)); 124 | } 125 | -------------------------------------------------------------------------------- /frontend/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: scrollwise_ai 2 | description: ScrollWise AI is a web application designed to help writers create and manage their stories in a comprehensive way. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=3.1.3 <4.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | # The following adds the Cupertino Icons font to your application. 34 | # Use with the CupertinoIcons class for iOS style icons. 35 | cupertino_icons: ^1.0.2 36 | intl: ^0.17.0 37 | http: ^1.2.2 38 | fluttertoast: ^8.2.8 39 | uuid: ^4.5.1 40 | file_picker: ^8.1.2 41 | jwt_decoder: ^2.0.1 42 | shared_preferences: ^2.3.2 43 | dotenv: ^4.2.0 44 | flutter_dotenv: ^5.0.2 45 | provider: ^6.1.2 46 | flutter_secure_storage: ^9.2.2 47 | expandable: ^5.0.1 48 | flutter_speed_dial: ^3.0.0 49 | url_launcher: ^6.2.5 50 | http_parser: ^4.0.2 51 | logging: ^1.2.0 52 | window_manager: ^0.4.0 53 | protocol_handler: ^0.1.5 54 | encrypt: ^5.0.3 55 | path: ^1.8.0 56 | pdf: ^3.10.4 57 | path_provider: ^2.1.1 58 | dropdown_search: ^5.0.6 59 | 60 | dev_dependencies: 61 | flutter_test: 62 | sdk: flutter 63 | 64 | # The "flutter_lints" package below contains a set of recommended lints to 65 | # encourage good coding practices. The lint set provided by the package is 66 | # activated in the `analysis_options.yaml` file located at the root of your 67 | # package. See that file for information about deactivating specific lint 68 | # rules and activating additional ones. 69 | flutter_lints: ^2.0.0 70 | flutter_launcher_icons: ^0.13.1 71 | 72 | # For information on the generic Dart part of this file, see the 73 | # following page: https://dart.dev/tools/pub/pubspec 74 | 75 | # The following section is specific to Flutter packages. 76 | flutter: 77 | # The following line ensures that the Material Icons font is 78 | # included with your application, so that you can use the icons in 79 | # the material Icons class. 80 | uses-material-design: true 81 | 82 | # To add assets to your application, add an assets section, like this: 83 | assets: 84 | - assets/images/logo.png 85 | # - images/a_dot_burr.jpeg 86 | # - images/a_dot_ham.jpeg 87 | 88 | # An image asset can refer to one or more resolution-specific "variants", see 89 | # https://flutter.dev/assets-and-images/#resolution-aware 90 | 91 | # For details regarding adding assets from package dependencies, see 92 | # https://flutter.dev/assets-and-images/#from-packages 93 | 94 | # To add custom fonts to your application, add a fonts section here, 95 | # in this "flutter" section. Each entry in this list should have a 96 | # "family" key with the font family name, and a "fonts" key with a 97 | # list giving the asset and other descriptors for the font. For 98 | # example: 99 | # fonts: 100 | # - family: Schyler 101 | # fonts: 102 | # - asset: fonts/Schyler-Regular.ttf 103 | # - asset: fonts/Schyler-Italic.ttf 104 | # style: italic 105 | # - family: Trajan Pro 106 | # fonts: 107 | # - asset: fonts/TrajanPro.ttf 108 | # - asset: fonts/TrajanPro_Bold.ttf 109 | # weight: 700 110 | # 111 | # For details regarding fonts from package dependencies, 112 | # see https://flutter.dev/custom-fonts/#from-packages 113 | 114 | flutter_launcher_icons: 115 | android: "ic_launcher" 116 | ios: true 117 | web: 118 | generate: true 119 | image_path: "assets/icon/icon.png" 120 | background_color: "#hexcolor" 121 | theme_color: "#hexcolor" 122 | image_path: "assets/icon/icon.png" 123 | -------------------------------------------------------------------------------- /frontend/lib/providers/relationship_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'dart:convert'; 4 | import '../utils/constants.dart'; 5 | 6 | import '../models/relationship.dart'; 7 | 8 | class RelationshipProvider extends ChangeNotifier { 9 | bool _isLoading = false; 10 | String? _error; 11 | String? _message; 12 | final Map _graphData = {}; 13 | List _relationships = []; 14 | 15 | bool get isLoading => _isLoading; 16 | String? get error => _error; 17 | String? get message => _message; 18 | Map get graphData => _graphData; 19 | List get relationships => _relationships; 20 | 21 | Future analyzeRelationships( 22 | List characterIds, String projectId) async { 23 | try { 24 | final response = await http.post( 25 | Uri.parse('$apiUrl/projects/$projectId/relationships/analyze'), 26 | headers: { 27 | 'Content-Type': 'application/json', 28 | }, 29 | body: json 30 | .encode(characterIds), // Send the character IDs in the request body 31 | ); 32 | 33 | if (response.statusCode == 200) { 34 | final data = json.decode(response.body); 35 | if (data['alreadyAnalyzed'] == true) { 36 | _message = data['message']; 37 | } else { 38 | await getRelationships( 39 | projectId); // Refresh relationships after analysis 40 | } 41 | notifyListeners(); 42 | } else { 43 | throw Exception('Failed to analyze relationships'); 44 | } 45 | } catch (e) { 46 | _error = e.toString(); 47 | notifyListeners(); 48 | rethrow; 49 | } 50 | } 51 | 52 | Future createRelationship({ 53 | required String character1Id, 54 | required String relatedCharacterId, 55 | required String relationshipType, 56 | required String projectId, 57 | String? description, 58 | }) async { 59 | try { 60 | final headers = {}; 61 | headers['Content-Type'] = 'application/json'; 62 | final response = await http.post( 63 | Uri.parse('$apiUrl/projects/$projectId/relationships/'), 64 | body: json.encode({ 65 | 'character_id': character1Id, 66 | 'related_character_id': relatedCharacterId, 67 | 'relationship_type': relationshipType, 68 | if (description != null) 'description': description, 69 | }), 70 | ); 71 | 72 | if (response.statusCode != 200) { 73 | throw Exception('Failed to create relationship'); 74 | } 75 | 76 | // Refresh relationships after creating 77 | await getRelationships(projectId); 78 | } catch (e) { 79 | rethrow; 80 | } 81 | } 82 | 83 | Future getRelationships(String projectId) async { 84 | _isLoading = true; 85 | _error = null; 86 | notifyListeners(); 87 | 88 | try { 89 | final response = await http.get( 90 | Uri.parse('$apiUrl/projects/$projectId/relationships/'), 91 | ); 92 | 93 | if (response.statusCode == 200) { 94 | final data = json.decode(response.body); 95 | if (data['relationships'] == null) { 96 | _relationships = []; 97 | } else { 98 | _relationships = (data['relationships'] as List) 99 | .map((json) { 100 | try { 101 | return Relationship.fromJson(json); 102 | } catch (e) { 103 | return null; 104 | } 105 | }) 106 | .where((relationship) => relationship != null) 107 | .cast() 108 | .toList(); 109 | } 110 | } else { 111 | final errorBody = json.decode(response.body); 112 | throw Exception(errorBody['detail'] ?? 'Failed to load relationships'); 113 | } 114 | } catch (e) { 115 | _error = 'An error occurred: $e'; 116 | } finally { 117 | _isLoading = false; 118 | notifyListeners(); 119 | } 120 | } 121 | 122 | Future updateRelationship( 123 | String relationshipId, String relationshipType, String projectId) async { 124 | try { 125 | final headers = {}; 126 | headers['Content-Type'] = 'application/json'; 127 | final response = await http.put( 128 | Uri.parse('$apiUrl/projects/$projectId/relationships/$relationshipId'), 129 | body: json.encode({ 130 | 'relationship_type': relationshipType, 131 | }), 132 | ); 133 | 134 | if (response.statusCode == 200) { 135 | await getRelationships(projectId); // Refresh the relationships list 136 | } else { 137 | throw Exception('Failed to update relationship'); 138 | } 139 | } catch (e) { 140 | _error = 'An error occurred: $e'; 141 | notifyListeners(); 142 | } 143 | } 144 | 145 | Future deleteRelationship( 146 | String relationshipId, String projectId) async { 147 | try { 148 | final response = await http.delete( 149 | Uri.parse('$apiUrl/projects/$projectId/relationships/$relationshipId'), 150 | ); 151 | 152 | if (response.statusCode == 200) { 153 | await getRelationships(projectId); // Refresh the relationships list 154 | } else { 155 | throw Exception('Failed to delete relationship'); 156 | } 157 | } catch (e) { 158 | _error = 'An error occurred: $e'; 159 | notifyListeners(); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /frontend/lib/widgets/location_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/location.dart'; 3 | 4 | class LocationDialog extends StatefulWidget { 5 | final Location? location; 6 | 7 | const LocationDialog({Key? key, this.location}) : super(key: key); 8 | 9 | @override 10 | State createState() => _LocationDialogState(); 11 | } 12 | 13 | class _LocationDialogState extends State { 14 | late TextEditingController _nameController; 15 | late TextEditingController _descriptionController; 16 | late TextEditingController _coordinatesController; 17 | late TextEditingController _significanceController; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _nameController = TextEditingController(text: widget.location?.name ?? ''); 23 | _descriptionController = 24 | TextEditingController(text: widget.location?.description ?? ''); 25 | _coordinatesController = 26 | TextEditingController(text: widget.location?.coordinates ?? ''); 27 | _significanceController = 28 | TextEditingController(text: widget.location?.significance ?? ''); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | _nameController.dispose(); 34 | _descriptionController.dispose(); 35 | _coordinatesController.dispose(); 36 | _significanceController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Dialog( 43 | child: Container( 44 | constraints: const BoxConstraints(maxWidth: 500), 45 | child: Padding( 46 | padding: const EdgeInsets.all(24), 47 | child: Column( 48 | mainAxisSize: MainAxisSize.min, 49 | crossAxisAlignment: CrossAxisAlignment.start, 50 | children: [ 51 | Row( 52 | children: [ 53 | CircleAvatar( 54 | backgroundColor: 55 | Theme.of(context).colorScheme.secondaryContainer, 56 | child: Icon( 57 | Icons.edit_location_alt, // Updated icon 58 | color: Theme.of(context).colorScheme.onSecondaryContainer, 59 | ), 60 | ), 61 | const SizedBox(width: 16), 62 | Text( 63 | widget.location == null 64 | ? 'Create Location' 65 | : 'Edit Location', 66 | style: Theme.of(context).textTheme.headlineSmall, 67 | ), 68 | ], 69 | ), 70 | const SizedBox(height: 24), 71 | TextField( 72 | controller: _nameController, 73 | decoration: InputDecoration( 74 | labelText: 'Name', 75 | border: OutlineInputBorder( 76 | borderRadius: BorderRadius.circular(8), 77 | ), 78 | prefixIcon: const Icon(Icons.label), 79 | ), 80 | ), 81 | const SizedBox(height: 16), 82 | TextField( 83 | controller: _descriptionController, 84 | decoration: InputDecoration( 85 | labelText: 'Description', 86 | border: OutlineInputBorder( 87 | borderRadius: BorderRadius.circular(8), 88 | ), 89 | prefixIcon: const Icon(Icons.description), 90 | ), 91 | maxLines: 3, 92 | ), 93 | const SizedBox(height: 16), 94 | TextField( 95 | controller: _coordinatesController, 96 | decoration: InputDecoration( 97 | labelText: 'Coordinates (optional)', 98 | border: OutlineInputBorder( 99 | borderRadius: BorderRadius.circular(8), 100 | ), 101 | prefixIcon: const Icon(Icons.map), 102 | hintText: 'e.g., 40.7128° N, 74.0060° W', 103 | ), 104 | ), 105 | const SizedBox(height: 16), 106 | TextField( 107 | controller: _significanceController, 108 | decoration: InputDecoration( 109 | labelText: 'Significance', 110 | border: OutlineInputBorder( 111 | borderRadius: BorderRadius.circular(8), 112 | ), 113 | prefixIcon: const Icon(Icons.star), 114 | ), 115 | maxLines: 2, 116 | ), 117 | const SizedBox(height: 24), 118 | Row( 119 | mainAxisAlignment: MainAxisAlignment.end, 120 | children: [ 121 | TextButton( 122 | onPressed: () => Navigator.pop(context), 123 | child: const Text('Cancel'), 124 | ), 125 | const SizedBox(width: 8), 126 | FilledButton.icon( 127 | icon: const Icon(Icons.save), 128 | label: const Text('Save'), 129 | onPressed: () { 130 | final result = { 131 | 'name': _nameController.text, 132 | 'description': _descriptionController.text, 133 | 'coordinates': _coordinatesController.text.isEmpty 134 | ? null 135 | : _coordinatesController.text, 136 | 'significance': _significanceController.text, 137 | }; 138 | Navigator.pop(context, result); 139 | }, 140 | ), 141 | ], 142 | ), 143 | ], 144 | ), 145 | ), 146 | ), 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /frontend/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "ScrollWise") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.lotus.scrollwise") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Copy the native assets provided by the build.dart from all packages. 127 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 128 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 129 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 130 | COMPONENT Runtime) 131 | 132 | # Fully re-copy the assets directory on each build to avoid having stale files 133 | # from a previous install. 134 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 135 | install(CODE " 136 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 137 | " COMPONENT Runtime) 138 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 139 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 140 | 141 | # Install the AOT library on non-Debug builds only. 142 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 143 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 144 | COMPONENT Runtime) 145 | endif() 146 | -------------------------------------------------------------------------------- /frontend/lib/widgets/character_relationship_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:expandable/expandable.dart'; 3 | import '../models/relationship.dart'; 4 | import '../widgets/edit_relationship_dialog.dart'; 5 | 6 | class CharacterRelationshipCard extends StatelessWidget { 7 | final String characterId; 8 | final String characterName; 9 | final List relationships; 10 | final Function(String) onDeleteRelationship; 11 | final Function(Relationship) onEditRelationship; 12 | 13 | const CharacterRelationshipCard({ 14 | Key? key, 15 | required this.characterId, 16 | required this.characterName, 17 | required this.relationships, 18 | required this.onDeleteRelationship, 19 | required this.onEditRelationship, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | final characterRelationships = relationships 25 | .where((relationship) => 26 | relationship.character1Id == characterId || 27 | relationship.character2Id == characterId) 28 | .toList(); 29 | 30 | if (characterRelationships.isEmpty) { 31 | return const SizedBox.shrink(); 32 | } 33 | 34 | return Card( 35 | margin: const EdgeInsets.symmetric(vertical: 8), 36 | elevation: 2, 37 | child: ExpandablePanel( 38 | theme: ExpandableThemeData( 39 | headerAlignment: ExpandablePanelHeaderAlignment.center, 40 | iconColor: Theme.of(context).colorScheme.primary, 41 | ), 42 | header: ListTile( 43 | leading: CircleAvatar( 44 | backgroundColor: Theme.of(context) 45 | .colorScheme 46 | .surfaceContainerHighest, // More subtle background 47 | child: Icon( 48 | Icons.person, 49 | color: Theme.of(context) 50 | .colorScheme 51 | .onSurfaceVariant, // More subtle icon color 52 | size: 20, // Slightly smaller icon 53 | ), 54 | ), 55 | title: Text( 56 | characterName, 57 | style: Theme.of(context).textTheme.titleMedium, 58 | ), 59 | subtitle: Text( 60 | '${characterRelationships.length} relationship(s)', 61 | style: Theme.of(context).textTheme.bodySmall, 62 | ), 63 | ), 64 | collapsed: const SizedBox.shrink(), 65 | expanded: Column( 66 | children: characterRelationships.map((relationship) { 67 | return _buildRelationshipTile(context, relationship); 68 | }).toList(), 69 | ), 70 | ), 71 | ); 72 | } 73 | 74 | Widget _buildRelationshipTile( 75 | BuildContext context, Relationship relationship) { 76 | return Container( 77 | decoration: BoxDecoration( 78 | border: Border( 79 | top: BorderSide( 80 | color: Theme.of(context).dividerColor, 81 | width: 0.5, 82 | ), 83 | ), 84 | ), 85 | child: ListTile( 86 | contentPadding: const EdgeInsets.symmetric( 87 | horizontal: 16, 88 | vertical: 8, 89 | ), 90 | title: Column( 91 | crossAxisAlignment: CrossAxisAlignment.start, 92 | children: [ 93 | Row( 94 | children: [ 95 | Icon( 96 | Icons.connect_without_contact, 97 | size: 20, 98 | color: Theme.of(context).colorScheme.primary, 99 | ), 100 | const SizedBox(width: 8), 101 | Expanded( 102 | child: Text( 103 | relationship.getOtherCharacterName(characterId), 104 | style: Theme.of(context).textTheme.titleSmall, 105 | ), 106 | ), 107 | ], 108 | ), 109 | const SizedBox(height: 4), 110 | Text( 111 | relationship.relationshipType, 112 | style: Theme.of(context).textTheme.bodyMedium?.copyWith( 113 | color: Theme.of(context).colorScheme.primary, 114 | fontWeight: FontWeight.bold, 115 | ), 116 | ), 117 | ], 118 | ), 119 | subtitle: Padding( 120 | padding: const EdgeInsets.only(top: 8), 121 | child: Text(relationship.description), 122 | ), 123 | trailing: Row( 124 | mainAxisSize: MainAxisSize.min, 125 | children: [ 126 | IconButton( 127 | icon: Icon( 128 | Icons.edit, 129 | color: Theme.of(context).colorScheme.primary, 130 | ), 131 | onPressed: () => _showEditDialog(context, relationship), 132 | ), 133 | IconButton( 134 | icon: Icon( 135 | Icons.delete, 136 | color: Theme.of(context).colorScheme.error, 137 | ), 138 | onPressed: () => _showDeleteDialog(context, relationship), 139 | ), 140 | ], 141 | ), 142 | ), 143 | ); 144 | } 145 | 146 | void _showEditDialog(BuildContext context, Relationship relationship) { 147 | showDialog( 148 | context: context, 149 | builder: (context) => EditRelationshipDialog( 150 | relationship: relationship, 151 | onEditRelationship: onEditRelationship, 152 | ), 153 | ); 154 | } 155 | 156 | void _showDeleteDialog(BuildContext context, Relationship relationship) { 157 | showDialog( 158 | context: context, 159 | builder: (context) => AlertDialog( 160 | title: const Text('Delete Relationship'), 161 | content: 162 | const Text('Are you sure you want to delete this relationship?'), 163 | actions: [ 164 | TextButton( 165 | onPressed: () => Navigator.pop(context), 166 | child: const Text('Cancel'), 167 | ), 168 | TextButton( 169 | onPressed: () { 170 | onDeleteRelationship(relationship.id); 171 | Navigator.pop(context); 172 | }, 173 | child: const Text('Delete'), 174 | ), 175 | ], 176 | ), 177 | ); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /frontend/lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../components/dashboard.dart'; 3 | import '../components/codex.dart'; 4 | import '../components/validity.dart'; 5 | import '../components/knowledge_base.dart'; 6 | import '../components/query.dart'; 7 | import '../components/create_chapter.dart'; 8 | import '../components/project_settings.dart'; 9 | import 'package:provider/provider.dart'; 10 | import '../providers/app_state.dart'; 11 | import '../components/editor.dart'; 12 | import '../components/codex_generation.dart'; 13 | 14 | import './character_relationships_screen.dart'; 15 | import './character_journey_screen.dart'; 16 | import './timeline_screen.dart'; 17 | 18 | class HomeScreen extends StatefulWidget { 19 | const HomeScreen({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _HomeScreenState(); 23 | } 24 | 25 | class _HomeScreenState extends State with TickerProviderStateMixin { 26 | TabController? _tabController; 27 | late Future _initFuture; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | _initFuture = Future.delayed(Duration.zero); 33 | } 34 | 35 | @override 36 | void dispose() { 37 | _tabController?.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | void _initializeTabController(List tabs) { 42 | _tabController?.dispose(); 43 | _tabController = TabController( 44 | length: tabs.length, 45 | vsync: this, 46 | initialIndex: 0, 47 | ); 48 | } 49 | 50 | List _getTabs() { 51 | return [ 52 | TabItem(icon: Icons.dashboard, label: 'Dashboard'), 53 | TabItem(icon: Icons.edit, label: 'Editor'), 54 | TabItem(icon: Icons.book, label: 'Codex'), 55 | TabItem(icon: Icons.auto_awesome, label: 'Codex Generation'), 56 | TabItem(icon: Icons.people, label: 'Character Relationships'), 57 | TabItem(icon: Icons.timeline, label: 'Character Journey'), 58 | TabItem(icon: Icons.schedule, label: 'Timeline'), 59 | TabItem(icon: Icons.check_circle, label: 'Validity'), 60 | TabItem(icon: Icons.psychology, label: 'Knowledge Base'), 61 | TabItem(icon: Icons.search, label: 'Query'), 62 | TabItem(icon: Icons.add_circle, label: 'Generate'), 63 | TabItem(icon: Icons.settings, label: 'Project Settings'), 64 | ]; 65 | } 66 | 67 | Widget _buildNoProjectSelected() { 68 | return Center( 69 | child: Column( 70 | mainAxisAlignment: MainAxisAlignment.center, 71 | children: [ 72 | Icon( 73 | Icons.folder_open, 74 | size: 64, 75 | color: Theme.of(context).colorScheme.secondary, 76 | ), 77 | const SizedBox(height: 16), 78 | Text( 79 | 'No Project Selected', 80 | style: Theme.of(context).textTheme.titleLarge?.copyWith( 81 | color: Theme.of(context).colorScheme.secondary, 82 | ), 83 | ), 84 | const SizedBox(height: 8), 85 | Text( 86 | 'Please select a project to get started.', 87 | textAlign: TextAlign.center, 88 | style: Theme.of(context).textTheme.bodyMedium, 89 | ), 90 | ], 91 | ), 92 | ); 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | final appState = Provider.of(context); 98 | final projectId = appState.currentProjectId; 99 | 100 | if (projectId == null) { 101 | return Scaffold(body: _buildNoProjectSelected()); 102 | } 103 | 104 | return FutureBuilder( 105 | future: _initFuture, 106 | builder: (context, snapshot) { 107 | final tabs = _getTabs(); 108 | 109 | if (_tabController?.length != tabs.length) { 110 | _initializeTabController(tabs); 111 | } 112 | 113 | if (_tabController == null) { 114 | return const Scaffold( 115 | body: Center(child: CircularProgressIndicator()), 116 | ); 117 | } 118 | 119 | return Scaffold( 120 | appBar: AppBar( 121 | elevation: 0, 122 | title: Row( 123 | children: [ 124 | Icon(Icons.book, color: Theme.of(context).colorScheme.primary), 125 | const SizedBox(width: 8), 126 | const Text('Story Builder'), 127 | ], 128 | ), 129 | actions: [ 130 | IconButton( 131 | icon: const Icon(Icons.exit_to_app), 132 | tooltip: 'Back to Projects', 133 | onPressed: () => 134 | Navigator.pushReplacementNamed(context, '/projects'), 135 | ), 136 | ], 137 | bottom: PreferredSize( 138 | preferredSize: const Size.fromHeight(48), 139 | child: TabBar( 140 | controller: _tabController, 141 | isScrollable: true, 142 | indicatorWeight: 3, 143 | indicatorColor: Theme.of(context).colorScheme.primary, 144 | tabs: tabs 145 | .map((tab) => Tab( 146 | icon: Icon(tab.icon, size: 20), 147 | text: tab.label, 148 | )) 149 | .toList(), 150 | ), 151 | ), 152 | ), 153 | body: TabBarView( 154 | controller: _tabController, 155 | physics: const NeverScrollableScrollPhysics(), 156 | children: tabs 157 | .map((tab) => _buildTabContent(tab.label, projectId)) 158 | .toList(), 159 | ), 160 | ); 161 | }, 162 | ); 163 | } 164 | 165 | Widget _buildTabContent(String tabLabel, String projectId) { 166 | switch (tabLabel) { 167 | case 'Dashboard': 168 | return Dashboard( 169 | projectId: projectId, 170 | onProgressChanged: (chapters, codexEntries, wordCount) { 171 | Provider.of(context, listen: false) 172 | .updateProgress(chapters, codexEntries, wordCount); 173 | }, 174 | ); 175 | case 'Editor': 176 | return Editor(projectId: projectId, readOnly: false); 177 | case 'Codex': 178 | return Codex(projectId: projectId); 179 | case 'Codex Generation': 180 | return CodexGeneration(projectId: projectId); 181 | case 'Character Relationships': 182 | return CharacterRelationshipsScreen(projectId: projectId); 183 | case 'Character Journey': 184 | return CharacterJourneyScreen(projectId: projectId); 185 | case 'Timeline': 186 | return TimelineScreen(projectId: projectId); 187 | case 'Validity': 188 | return Validity(projectId: projectId); 189 | case 'Knowledge Base': 190 | return KnowledgeBase(projectId: projectId); 191 | case 'Query': 192 | return Query(projectId: projectId); 193 | case 'Generate': 194 | return CreateChapter(projectId: projectId); 195 | case 'Project Settings': 196 | return ProjectSettings(projectId: projectId); 197 | default: 198 | return const Center(child: Text('Unknown tab')); 199 | } 200 | } 201 | } 202 | 203 | class TabItem { 204 | final IconData icon; 205 | final String label; 206 | 207 | TabItem({required this.icon, required this.label}); 208 | } 209 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.1 4 | 5 | - Implemented a dynamic dashboard that seamlessly populates with information retrieved from the knowledge base, including chapter summaries, character descriptions, and world-building details, utilizing the `generate_with_retrieval` function. 6 | 7 | ## 0.0.2 8 | 9 | - Integrated a search bar into the dashboard, enabling users to efficiently locate specific information within the knowledge base. 10 | 11 | ## 0.0.3 12 | 13 | - Added functionality for users to create new chapters and update the knowledge base accordingly, ensuring data consistency and facilitating story progression. 14 | 15 | ## 0.0.4 16 | 17 | - Implemented a comprehensive character management system, allowing users to create, edit, and manage character profiles within the knowledge base. 18 | 19 | ## 0.0.5 20 | 21 | - Enhanced character management by enabling users to delete characters from the knowledge base, providing greater control over story elements. 22 | 23 | ## 0.0.6 24 | 25 | - Introduced a world-building feature, empowering users to view and edit details such as history, culture, and geography, enriching the story's context. 26 | 27 | ## 0.0.7 28 | 29 | - Restructured the organization of characters, world-building elements, and items into a dedicated "Codex" section, improving the app's overall structure and navigation. 30 | 31 | ## 0.0.8 32 | 33 | - Implemented knowledge base import functionality, allowing users to seamlessly integrate existing information from files such as PDFs and Markdown documents. 34 | 35 | ## 0.0.9 36 | 37 | - Revamped the landing page with a focus on enhanced visual appeal and user-friendliness, creating a more welcoming and intuitive user experience. 38 | 39 | ## 0.0.10 40 | 41 | - Introduced project management capabilities, enabling users to create, read, update, and delete projects. Each project functions as a separate instance of the application's frontend, utilizing a unique project ID for backend communication. User login now directs to a project dashboard, providing access to project-specific homescreens, chat histories, chapters, and other data. Settings remain global and are not project-specific. 42 | 43 | ## 0.0.11 44 | 45 | - Integrated project settings functionality, allowing users to manage project-specific details such as name, description, and API key, eliminating the need for manual API key entry during project creation. 46 | 47 | ## 0.0.12 48 | 49 | - Upgraded the database to PostgreSQL, improving data management, performance, and scalability. 50 | 51 | ## 0.0.13 52 | 53 | - Optimized the web application for graceful rendering on mobile devices, ensuring a consistent and user-friendly experience across different platforms. 54 | 55 | ## 0.0.14 56 | 57 | - Transitioned the frontend development framework from React to Flutter, leveraging Flutter's cross-platform capabilities and performance benefits. 58 | 59 | ## 0.0.15 60 | 61 | - Resolved an issue where duplicate chapters were being generated or saved in the editor. 62 | 63 | ## 0.0.16 64 | 65 | - Connected the `_generate_title` function from the Agent Manager to the chapter title in the database, ensuring accurate title generation and storage. 66 | 67 | ## 0.0.17 68 | 69 | - Addressed a problem where characters were being saved redundantly, even if they already existed in the database. 70 | 71 | ## 0.0.18 72 | 73 | - Added titles to validity checks, improving clarity and organization. 74 | 75 | ## 0.0.19 76 | 77 | - Resolved an error that prevented chapter generation, even when the chapter was successfully generated. 78 | 79 | ## 0.0.20 80 | 81 | - Addressed an issue where presets were throwing errors even when successfully loaded. 82 | 83 | ## 0.0.21 84 | 85 | - Implemented a changelog to track changes and updates within the application, providing users with a clear overview of new features and improvements. 86 | 87 | ## 0.0.22 88 | 89 | - Added a progress indicator for chapter generation, allowing users to monitor the generation process, cancel if necessary, and prevent accidental multiple chapter generation. 90 | 91 | ## 0.0.23 92 | 93 | - Introduced the concept of universes, enabling users to create and manage multiple universes within the project, each with its own knowledge base, characters, and lore. 94 | 95 | ## 0.0.24 96 | 97 | - Implemented AI-powered Codex generation, allowing users to generate new characters, items, and locations based on the existing knowledge base. 98 | 99 | ## 0.0.25 100 | 101 | - Resolved an issue where chat history was not being saved or displayed correctly. 102 | 103 | ## 0.0.26 104 | 105 | - Fixed a bug that prevented presets from loading properly. 106 | 107 | ## 0.0.27 108 | 109 | - Implemented story progress tracking features, allowing users to monitor chapter count, character count, word count, and set goals for their stories. 110 | 111 | ## 0.0.28 112 | 113 | - Added a story timeline feature that visualizes the order of events and relationships between characters and chapters. 114 | 115 | ## 0.0.29 116 | 117 | - Integrated an interactive world map feature, enabling users to visualize and track locations of characters and events within the story's world. 118 | 119 | ## 0.0.30 120 | 121 | - Enhanced the Codex system with character journey tracking, character arc visualization, relationships. 122 | 123 | ## 0.0.31 124 | 125 | - Implemented safeguards in the Create section to prevent multiple simultaneous chapter generations by disabling the generate button during processing. 126 | 127 | ## 0.0.32 128 | 129 | - Completely revamped the application's styling for improved visual appeal and user experience. 130 | 131 | ## 0.0.33 132 | 133 | - Enhanced security by implementing proper API key storage and access controls, ensuring users can only access their own API keys. 134 | 135 | ## 0.0.34 136 | 137 | - Optimized server.py to handle concurrent users efficiently, enabling smooth multi-user functionality. 138 | 139 | ## Recent Changes 140 | 141 | ### Refactor 142 | 143 | - Migrated `update_codex_item` to use SQLAlchemy (38ee798) 144 | - Migrated `delete_codex_item` to use SQLAlchemy (81b3c40) 145 | - Migrated `get_codex_item_by_id` to use SQLAlchemy (1bb33c5) 146 | - Use SQLAlchemy for API key storage (a3cf53c) 147 | - Use SQLAlchemy to get and decrypt API key (ada1eaa) 148 | - Remove API key using SQLAlchemy (ee0c581) 149 | - Migrated `save_model_settings` to use SQLAlchemy (790f9b3) 150 | - Update `create_location` to use SQLAlchemy (022e1b5) 151 | - Migrated `delete_location` method to use SQLAlchemy (b5f12aa) 152 | 153 | ### Fixes 154 | 155 | - Handle missing user in `save_api_key` (7766436) 156 | 157 | ## 0.0.40 158 | 159 | - Migrated database from Remote to being local 160 | 161 | ## 1.0.2-beta 162 | 163 | - Fix some issues I came across 164 | 165 | ## 1.0.3-beta 166 | 167 | - Better logging and error handling 168 | - Fixed presets not saving correctly 169 | - Fixed Universes not creating correctly 170 | - Remove approval check and waitlisting 171 | - Update frontend to use scaffoldMessenger instead of AppNotification 172 | - Better session management 173 | - Fix uploading documents to knowledge base and editor 174 | - Better server health check 175 | - Remove unused screens 176 | - Fix locations, relationships, and events not working properly 177 | 178 | ## 1.0.4-beta 179 | 180 | - Fixed users not being able to log in 181 | 182 | ## 1.0.5-beta 183 | 184 | - Fixed a bug where the app would crash when the user had too many knowledge base items 185 | 186 | - Migrated to Qdrant 187 | 188 | - Added vector store backing 189 | 190 | ## 1.0.6 191 | 192 | - Removed Supabase 193 | 194 | ## 1.0.7 195 | 196 | - Add Openrouter models 197 | -------------------------------------------------------------------------------- /frontend/lib/screens/landing_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:math' as math; 3 | import 'package:flutter/scheduler.dart'; 4 | import 'package:url_launcher/url_launcher.dart'; 5 | 6 | class LandingScreen extends StatelessWidget { 7 | const LandingScreen({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Scaffold( 12 | appBar: AppBar( 13 | elevation: 0, 14 | backgroundColor: Colors.transparent, 15 | ), 16 | body: AnimatedBackground( 17 | child: SingleChildScrollView( 18 | child: Column( 19 | children: [ 20 | _buildWelcomeSection(context), 21 | _buildTutorialSection(context), 22 | ], 23 | ), 24 | ), 25 | ), 26 | ); 27 | } 28 | 29 | Widget _buildWelcomeSection(BuildContext context) { 30 | return Container( 31 | padding: const EdgeInsets.all(48), 32 | child: Column( 33 | children: [ 34 | Image.asset( 35 | 'assets/images/logo.png', 36 | width: 120, 37 | height: 120, 38 | ), 39 | const SizedBox(height: 24), 40 | Text( 41 | 'Welcome to ScrollWise', 42 | style: Theme.of(context).textTheme.displayMedium, 43 | ), 44 | const SizedBox(height: 16), 45 | Text( 46 | 'Your AI-Powered Writing Studio', 47 | style: Theme.of(context).textTheme.headlineSmall, 48 | ), 49 | const SizedBox(height: 32), 50 | _buildFeaturesList(context), 51 | const SizedBox(height: 32), 52 | FilledButton.icon( 53 | onPressed: () => Navigator.pushReplacementNamed( 54 | context, '/projects'), // Navigate directly to projects 55 | icon: const Icon(Icons.rocket_launch_outlined), // Changed icon 56 | label: const Text('Go to Projects'), // Changed text 57 | ), 58 | const SizedBox(height: 16), 59 | Text( 60 | 'Beta v1.0.7', 61 | style: Theme.of(context).textTheme.bodySmall?.copyWith( 62 | color: Theme.of(context).colorScheme.primary, 63 | ), 64 | ), 65 | ], 66 | ), 67 | ); 68 | } 69 | 70 | Widget _buildFeaturesList(BuildContext context) { 71 | final features = [ 72 | '🤖 AI-Powered Writing Assistance', 73 | '📚 Dynamic Story Dashboard', 74 | '🎭 Character Management & Journey Tracking', 75 | '🗺️ Interactive World Building', 76 | '📊 Story Progress Tracking', 77 | '📅 Timeline Visualization', 78 | ]; 79 | 80 | return Wrap( 81 | alignment: WrapAlignment.center, 82 | spacing: 16, 83 | runSpacing: 8, 84 | children: features 85 | .map((feature) => Chip( 86 | label: Text(feature), 87 | backgroundColor: Theme.of(context).colorScheme.primaryContainer, 88 | labelStyle: TextStyle( 89 | color: Theme.of(context).colorScheme.onPrimaryContainer, 90 | ), 91 | )) 92 | .toList(), 93 | ); 94 | } 95 | 96 | Widget _buildTutorialSection(BuildContext context) { 97 | return Container( 98 | padding: const EdgeInsets.all(48), 99 | child: Column( 100 | children: [ 101 | Text( 102 | 'Join Our Community', 103 | style: Theme.of(context).textTheme.headlineSmall, 104 | ), 105 | const SizedBox(height: 24), 106 | Row( 107 | mainAxisAlignment: MainAxisAlignment.center, 108 | children: [ 109 | IconButton.filled( 110 | onPressed: () async { 111 | final Uri url = Uri.parse('https://discord.gg/R8PUtxFPUq'); 112 | if (!await launchUrl(url)) { 113 | throw Exception('Could not launch $url'); 114 | } 115 | }, 116 | icon: const Icon(Icons.discord), 117 | iconSize: 32, 118 | ), 119 | const SizedBox(width: 16), 120 | IconButton.filled( 121 | onPressed: () async { 122 | final Uri url = 123 | Uri.parse('https://github.com/LotusSerene/scrollwise-ai'); 124 | if (!await launchUrl(url)) { 125 | throw Exception('Could not launch $url'); 126 | } 127 | }, 128 | icon: const Icon(Icons.developer_mode), 129 | iconSize: 32, 130 | ), 131 | ], 132 | ), 133 | ], 134 | ), 135 | ); 136 | } 137 | } 138 | 139 | class AnimatedBackground extends StatefulWidget { 140 | final Widget child; 141 | const AnimatedBackground({Key? key, required this.child}) : super(key: key); 142 | 143 | @override 144 | State createState() => _AnimatedBackgroundState(); 145 | } 146 | 147 | class _AnimatedBackgroundState extends State 148 | with SingleTickerProviderStateMixin { 149 | late final Ticker _ticker; 150 | final List _bubbles = []; 151 | final int _bubbleCount = 30; 152 | 153 | @override 154 | void initState() { 155 | super.initState(); 156 | _initializeBubbles(); 157 | _ticker = createTicker(_onTick)..start(); 158 | } 159 | 160 | void _initializeBubbles() { 161 | final random = math.Random(); 162 | for (int i = 0; i < _bubbleCount; i++) { 163 | _bubbles.add(Bubble( 164 | position: Offset( 165 | random.nextDouble(), 166 | random.nextDouble(), 167 | ), 168 | size: random.nextDouble() * 30 + 10, 169 | speed: random.nextDouble() * 0.001 + 0.001, 170 | color: HSLColor.fromAHSL( 171 | 0.2, 172 | random.nextDouble() * 360, 173 | 0.6, 174 | 0.7, 175 | ).toColor(), 176 | )); 177 | } 178 | } 179 | 180 | void _onTick(Duration elapsed) { 181 | setState(() { 182 | for (var bubble in _bubbles) { 183 | bubble.position = Offset( 184 | bubble.position.dx, 185 | bubble.position.dy - bubble.speed, 186 | ); 187 | 188 | if (bubble.position.dy < -0.1) { 189 | bubble.position = Offset( 190 | bubble.position.dx, 191 | 1.1, 192 | ); 193 | } 194 | } 195 | }); 196 | } 197 | 198 | @override 199 | Widget build(BuildContext context) { 200 | return Stack( 201 | children: [ 202 | Container( 203 | decoration: BoxDecoration( 204 | gradient: LinearGradient( 205 | begin: Alignment.topLeft, 206 | end: Alignment.bottomRight, 207 | colors: [ 208 | Theme.of(context).colorScheme.primary.withOpacity(0.1), 209 | Theme.of(context).colorScheme.secondary.withOpacity(0.1), 210 | Theme.of(context).colorScheme.surface, 211 | ], 212 | ), 213 | ), 214 | ), 215 | CustomPaint( 216 | painter: BubblePainter( 217 | bubbles: _bubbles, 218 | color: Theme.of(context).colorScheme.primary, 219 | ), 220 | size: Size.infinite, 221 | ), 222 | widget.child, 223 | ], 224 | ); 225 | } 226 | 227 | @override 228 | void dispose() { 229 | _ticker.dispose(); 230 | super.dispose(); 231 | } 232 | } 233 | 234 | class Bubble { 235 | Offset position; 236 | final double size; 237 | final double speed; 238 | final Color color; 239 | 240 | Bubble({ 241 | required this.position, 242 | required this.size, 243 | required this.speed, 244 | required this.color, 245 | }); 246 | } 247 | 248 | class BubblePainter extends CustomPainter { 249 | final List bubbles; 250 | final Color color; 251 | 252 | const BubblePainter({ 253 | required this.bubbles, 254 | required this.color, 255 | }); 256 | 257 | @override 258 | void paint(Canvas canvas, Size size) { 259 | for (var bubble in bubbles) { 260 | final paint = Paint() 261 | ..color = bubble.color 262 | ..style = PaintingStyle.fill; 263 | 264 | canvas.drawCircle( 265 | Offset( 266 | bubble.position.dx * size.width, 267 | bubble.position.dy * size.height, 268 | ), 269 | bubble.size, 270 | paint, 271 | ); 272 | } 273 | } 274 | 275 | @override 276 | bool shouldRepaint(covariant BubblePainter oldDelegate) => true; 277 | } 278 | -------------------------------------------------------------------------------- /frontend/lib/widgets/event_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../models/event.dart'; 3 | import 'package:intl/intl.dart'; 4 | 5 | class EventDialog extends StatefulWidget { 6 | final Event? event; 7 | 8 | const EventDialog({Key? key, this.event}) : super(key: key); 9 | 10 | @override 11 | State createState() => _EventDialogState(); 12 | } 13 | 14 | class _EventDialogState extends State { 15 | late TextEditingController _titleController; 16 | late TextEditingController _descriptionController; 17 | late TextEditingController _impactController; 18 | late DateTime _selectedDate; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _titleController = TextEditingController(text: widget.event?.title ?? ''); 24 | _descriptionController = 25 | TextEditingController(text: widget.event?.description ?? ''); 26 | _impactController = TextEditingController(text: widget.event?.impact ?? ''); 27 | _selectedDate = widget.event?.date ?? DateTime.now(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | _titleController.dispose(); 33 | _descriptionController.dispose(); 34 | _impactController.dispose(); 35 | super.dispose(); 36 | } 37 | 38 | Future _selectDate(BuildContext context) async { 39 | final DateTime? picked = await showDatePicker( 40 | context: context, 41 | initialDate: _selectedDate, 42 | firstDate: DateTime(1000), 43 | lastDate: DateTime(3000), 44 | ); 45 | if (picked != null && picked != _selectedDate) { 46 | setState(() { 47 | _selectedDate = picked; 48 | }); 49 | } 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return Dialog( 55 | child: Container( 56 | constraints: const BoxConstraints(maxWidth: 500), 57 | child: SingleChildScrollView( 58 | child: Padding( 59 | padding: const EdgeInsets.all(24), 60 | child: Column( 61 | mainAxisSize: MainAxisSize.min, 62 | crossAxisAlignment: CrossAxisAlignment.start, 63 | children: [ 64 | Row( 65 | children: [ 66 | Container( 67 | padding: const EdgeInsets.all(12), 68 | decoration: BoxDecoration( 69 | color: Theme.of(context).colorScheme.primaryContainer, 70 | borderRadius: BorderRadius.circular(12), 71 | ), 72 | child: Icon( 73 | Icons.edit_calendar, 74 | color: Theme.of(context).colorScheme.onPrimaryContainer, 75 | size: 28, 76 | ), 77 | ), 78 | const SizedBox(width: 16), 79 | Expanded( 80 | child: Column( 81 | crossAxisAlignment: CrossAxisAlignment.start, 82 | children: [ 83 | Text( 84 | widget.event == null 85 | ? 'Create Event' 86 | : 'Edit Event', 87 | style: Theme.of(context).textTheme.headlineSmall, 88 | ), 89 | Text( 90 | 'Enter event details below', 91 | style: Theme.of(context) 92 | .textTheme 93 | .bodyMedium 94 | ?.copyWith( 95 | color: Theme.of(context) 96 | .colorScheme 97 | .onSurface 98 | .withOpacity(0.7), 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ], 105 | ), 106 | const SizedBox(height: 24), 107 | TextField( 108 | controller: _titleController, 109 | decoration: InputDecoration( 110 | labelText: 'Title', 111 | hintText: 'Enter event title', 112 | border: OutlineInputBorder( 113 | borderRadius: BorderRadius.circular(12), 114 | ), 115 | prefixIcon: const Icon(Icons.title), 116 | ), 117 | ), 118 | const SizedBox(height: 16), 119 | TextField( 120 | controller: _descriptionController, 121 | maxLines: 3, 122 | decoration: InputDecoration( 123 | labelText: 'Description', 124 | hintText: 'Describe the event', 125 | border: OutlineInputBorder( 126 | borderRadius: BorderRadius.circular(12), 127 | ), 128 | prefixIcon: const Icon(Icons.description), 129 | alignLabelWithHint: true, 130 | ), 131 | ), 132 | const SizedBox(height: 16), 133 | TextField( 134 | controller: _impactController, 135 | maxLines: 2, 136 | decoration: InputDecoration( 137 | labelText: 'Impact', 138 | hintText: 'Describe the event\'s impact', 139 | border: OutlineInputBorder( 140 | borderRadius: BorderRadius.circular(12), 141 | ), 142 | prefixIcon: const Icon(Icons.change_history), 143 | alignLabelWithHint: true, 144 | ), 145 | ), 146 | const SizedBox(height: 16), 147 | InkWell( 148 | onTap: () => _selectDate(context), 149 | borderRadius: BorderRadius.circular(12), 150 | child: Container( 151 | padding: const EdgeInsets.symmetric( 152 | horizontal: 12, vertical: 16), 153 | decoration: BoxDecoration( 154 | border: Border.all( 155 | color: Theme.of(context).colorScheme.outline), 156 | borderRadius: BorderRadius.circular(12), 157 | ), 158 | child: Row( 159 | children: [ 160 | Icon(Icons.calendar_today, 161 | color: Theme.of(context).colorScheme.primary), 162 | const SizedBox(width: 12), 163 | Column( 164 | crossAxisAlignment: CrossAxisAlignment.start, 165 | children: [ 166 | Text('Date', 167 | style: Theme.of(context).textTheme.labelMedium), 168 | Text( 169 | DateFormat.yMMMd().format(_selectedDate), 170 | style: Theme.of(context).textTheme.titleMedium, 171 | ), 172 | ], 173 | ), 174 | ], 175 | ), 176 | ), 177 | ), 178 | const SizedBox(height: 24), 179 | Row( 180 | mainAxisAlignment: MainAxisAlignment.end, 181 | children: [ 182 | OutlinedButton.icon( 183 | icon: const Icon(Icons.close), 184 | label: const Text('Cancel'), 185 | onPressed: () => Navigator.pop(context), 186 | ), 187 | const SizedBox(width: 12), 188 | FilledButton.icon( 189 | icon: const Icon(Icons.save), 190 | label: const Text('Save Event'), 191 | onPressed: () { 192 | final result = { 193 | 'title': _titleController.text, 194 | 'description': _descriptionController.text, 195 | 'date': _selectedDate.toIso8601String(), 196 | 'impact': _impactController.text, 197 | }; 198 | Navigator.pop(context, result); 199 | }, 200 | ), 201 | ], 202 | ), 203 | ], 204 | ), 205 | ), 206 | ), 207 | ), 208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /backend/api_key_manager.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from datetime import datetime, timezone 4 | from typing import Optional 5 | from pathlib import Path 6 | from fastapi import HTTPException 7 | from cryptography.fernet import Fernet 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class SecurityManager: 13 | def __init__(self): 14 | self.key_path = Path(os.path.dirname(__file__)) / "encryption.key" 15 | self.fernet = self._initialize_encryption() 16 | 17 | def _initialize_encryption(self) -> Fernet: 18 | try: 19 | # Try to load existing key 20 | if self.key_path.exists(): 21 | encryption_key = self.key_path.read_bytes() 22 | else: 23 | # Generate new key if none exists 24 | encryption_key = Fernet.generate_key() 25 | # Save key securely with restricted permissions 26 | self.key_path.write_bytes(encryption_key) 27 | # Set file permissions (on Unix systems) 28 | if os.name != "nt": # not Windows 29 | self.key_path.chmod(0o600) 30 | logger.info("Generated new encryption key") 31 | 32 | return Fernet(encryption_key) 33 | 34 | except Exception as e: 35 | logger.error(f"Error initializing encryption: {e}") 36 | raise 37 | 38 | def encrypt_data(self, data: str) -> str: 39 | try: 40 | return self.fernet.encrypt(data.encode()).decode() 41 | except Exception as e: 42 | logger.error(f"Error encrypting data: {e}") 43 | raise 44 | 45 | def decrypt_data(self, encrypted_data: str) -> str: 46 | try: 47 | return self.fernet.decrypt(encrypted_data.encode()).decode() 48 | except Exception as e: 49 | logger.error(f"Decryption failed: {type(e).__name__} - {str(e)}") 50 | logger.error(f"Encrypted data length: {len(encrypted_data)}") 51 | raise ValueError( 52 | "Failed to decrypt data - may be corrupted or using wrong encryption key" 53 | ) from e 54 | 55 | 56 | class ApiKeyManager: 57 | # Existing file for Google API Key 58 | API_KEY_FILE = Path("./api_key.dat") 59 | # New file for OpenRouter API Key 60 | OPENROUTER_API_KEY_FILE = Path("./openrouter_api_key.dat") 61 | 62 | def __init__(self, security_manager: SecurityManager): 63 | self.security_manager = security_manager 64 | 65 | # --- Google API Key Methods --- 66 | 67 | async def save_api_key(self, api_key: str) -> None: 68 | """Saves the Google API key.""" 69 | if api_key is None: 70 | logger.error("API key cannot be None") 71 | raise HTTPException(status_code=400, detail="API key cannot be None") 72 | try: 73 | encrypted_key = self.security_manager.encrypt_data(api_key) 74 | self.API_KEY_FILE.write_text(encrypted_key) 75 | logger.info(f"Google API key saved locally to {self.API_KEY_FILE}") 76 | except IOError as e: 77 | logger.error(f"Error writing Google API key file: {e}") 78 | raise HTTPException( 79 | status_code=500, detail="Error saving Google API key file" 80 | ) 81 | except Exception as e: 82 | logger.error(f"Error saving Google API key: {e}") 83 | raise HTTPException(status_code=500, detail="Error saving Google API key") 84 | 85 | async def get_api_key(self) -> Optional[str]: 86 | """Gets the Google API key.""" 87 | try: 88 | if not self.API_KEY_FILE.exists(): 89 | logger.info("Local Google API key file not found.") 90 | return None 91 | 92 | encrypted_key = self.API_KEY_FILE.read_text() 93 | if not encrypted_key: 94 | logger.warning("Local Google API key file is empty.") 95 | return None 96 | 97 | try: 98 | decrypted_key = self.security_manager.decrypt_data(encrypted_key) 99 | return decrypted_key 100 | except ValueError as e: 101 | logger.error(f"Decryption failed for local Google API key: {e}") 102 | return None 103 | except Exception as e: 104 | logger.error(f"Unexpected error during Google key decryption: {e}") 105 | return None 106 | 107 | except IOError as e: 108 | logger.error(f"Error reading Google API key file: {e}") 109 | return None 110 | except Exception as e: 111 | logger.error(f"Error retrieving Google API key: {e}") 112 | return None 113 | 114 | async def remove_api_key(self) -> None: 115 | """Removes the locally stored Google API key file.""" 116 | try: 117 | if self.API_KEY_FILE.exists(): 118 | self.API_KEY_FILE.unlink() 119 | logger.info(f"Local Google API key file removed: {self.API_KEY_FILE}") 120 | else: 121 | logger.info("Local Google API key file not found, nothing to remove.") 122 | except IOError as e: 123 | logger.error(f"Error removing Google API key file: {e}") 124 | raise HTTPException( 125 | status_code=500, detail="Error removing Google API key file" 126 | ) 127 | except Exception as e: 128 | logger.error(f"Error removing Google API key: {e}") 129 | raise HTTPException(status_code=500, detail="Error removing Google API key") 130 | 131 | # --- OpenRouter API Key Methods --- 132 | 133 | async def save_openrouter_api_key(self, api_key: str) -> None: 134 | """Saves the OpenRouter API key.""" 135 | if api_key is None: 136 | logger.error("OpenRouter API key cannot be None") 137 | raise HTTPException( 138 | status_code=400, detail="OpenRouter API key cannot be None" 139 | ) 140 | try: 141 | encrypted_key = self.security_manager.encrypt_data(api_key) 142 | self.OPENROUTER_API_KEY_FILE.write_text(encrypted_key) 143 | logger.info( 144 | f"OpenRouter API key saved locally to {self.OPENROUTER_API_KEY_FILE}" 145 | ) 146 | except IOError as e: 147 | logger.error(f"Error writing OpenRouter API key file: {e}") 148 | raise HTTPException( 149 | status_code=500, detail="Error saving OpenRouter API key file" 150 | ) 151 | except Exception as e: 152 | logger.error(f"Error saving OpenRouter API key: {e}") 153 | raise HTTPException( 154 | status_code=500, detail="Error saving OpenRouter API key" 155 | ) 156 | 157 | async def get_openrouter_api_key(self) -> Optional[str]: 158 | """Gets the OpenRouter API key.""" 159 | try: 160 | if not self.OPENROUTER_API_KEY_FILE.exists(): 161 | logger.info("Local OpenRouter API key file not found.") 162 | return None 163 | 164 | encrypted_key = self.OPENROUTER_API_KEY_FILE.read_text() 165 | if not encrypted_key: 166 | logger.warning("Local OpenRouter API key file is empty.") 167 | return None 168 | 169 | try: 170 | decrypted_key = self.security_manager.decrypt_data(encrypted_key) 171 | return decrypted_key 172 | except ValueError as e: 173 | logger.error(f"Decryption failed for local OpenRouter API key: {e}") 174 | return None 175 | except Exception as e: 176 | logger.error(f"Unexpected error during OpenRouter key decryption: {e}") 177 | return None 178 | 179 | except IOError as e: 180 | logger.error(f"Error reading OpenRouter API key file: {e}") 181 | return None 182 | except Exception as e: 183 | logger.error(f"Error retrieving OpenRouter API key: {e}") 184 | return None 185 | 186 | async def remove_openrouter_api_key(self) -> None: 187 | """Removes the locally stored OpenRouter API key file.""" 188 | try: 189 | if self.OPENROUTER_API_KEY_FILE.exists(): 190 | self.OPENROUTER_API_KEY_FILE.unlink() 191 | logger.info( 192 | f"Local OpenRouter API key file removed: {self.OPENROUTER_API_KEY_FILE}" 193 | ) 194 | else: 195 | logger.info( 196 | "Local OpenRouter API key file not found, nothing to remove." 197 | ) 198 | except IOError as e: 199 | logger.error(f"Error removing OpenRouter API key file: {e}") 200 | raise HTTPException( 201 | status_code=500, detail="Error removing OpenRouter API key file" 202 | ) 203 | except Exception as e: 204 | logger.error(f"Error removing OpenRouter API key: {e}") 205 | raise HTTPException( 206 | status_code=500, detail="Error removing OpenRouter API key" 207 | ) 208 | --------------------------------------------------------------------------------