diff --git a/.gitignore b/.gitignore index a3ed5e9..e844225 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ Thumbs.db # Claude specific **/.claude/settings.local.json +private-dev/ diff --git a/CHAT_SETUP.md b/CHAT_SETUP.md new file mode 100644 index 0000000..a95a873 --- /dev/null +++ b/CHAT_SETUP.md @@ -0,0 +1,316 @@ +# BERT LLM Chat Integration + +## Overview +This document describes the LLM chat integration feature for the BERT application, which allows users to interact with loaded system models through natural language queries. + +## Architecture + +### Frontend Components +- **File**: `src/leptos_app/components/chat.rs` +- **Purpose**: Reactive chat UI component built with Leptos +- **Key Features**: + - Environment detection (Tauri desktop vs web browser) + - Automatic model context loading + - Real-time message display + - Proper async state management + +### Backend Service +- **File**: `src-tauri/src/chat_service.rs` +- **Purpose**: LLM provider abstraction and model management +- **Key Features**: + - Multiple LLM provider support (Ollama, OpenAI, Mock) + - Automatic model data extraction and context management + - Enhanced factual response generation + - Graceful fallback handling + +### Integration Points +- **File**: `src-tauri/src/lib.rs` +- **Tauri Commands**: + - `chat_with_model(message, context)` - Main chat endpoint + - `get_current_model()` - Retrieve loaded model data + - `update_current_model(data)` - Store model data for chat context +- **File**: `src-tauri/src/data_model/load.rs` + - Auto-detection and storage of JSON model data during file loading + +## LLM Provider Support + +### 1. Local LLM (Ollama) - Default & Recommended +```toml +# In src-tauri/Cargo.toml +ollama-rs = "0.2" +tokio = "1.0" +``` + +**Setup Instructions**: +1. Install Ollama: `curl -fsSL https://ollama.ai/install.sh | sh` +2. Pull model: `ollama pull llama3.2:3b` +3. Start Ollama service: `ollama serve` +4. Build with local LLM support: `cargo tauri dev --features local-llm` + +**Benefits**: +- Complete privacy (no data sent to cloud) +- No API costs +- Works offline +- Customizable system prompts + +### 2. Cloud API (OpenAI) - Optional +```toml +# In src-tauri/Cargo.toml (when enabled) +async-openai = "0.28" +``` + +**Setup Instructions**: +1. Get OpenAI API key +2. Set environment variable: `export OPENAI_API_KEY="your-key"` +3. Build with cloud API: `cargo tauri dev --features cloud-api` + +**Benefits**: +- Instant responses +- High-quality analysis +- No local compute required + +### 3. Mock Provider - Fallback +- Provides enhanced sample responses when no LLM is available +- Includes Bitcoin-specific examples +- Used for development/testing + +## Implementation History & Fixes + +### Initial Implementation Challenges +1. **Thread Safety Issues**: Fixed mutex handling in async contexts +2. **Leptos API Compatibility**: Updated from deprecated `create_signal()` to `signal()` +3. **WebAssembly Constraints**: Changed from `Action::new` to `Action::new_unsync`, then to direct `spawn_local` +4. **Environment Detection**: Added `is_tauri_environment()` for desktop vs web compatibility + +### Model Context Integration +- **Problem**: Chat was using hardcoded sample data instead of loaded JSON models +- **Solution**: + - Modified `load_file` command to auto-detect and store JSON model data + - Added `get_current_model()` / `update_current_model()` commands + - Integrated model data extraction into chat context + +### Response Quality Improvements +- **Problem**: LLM responses were interpretive ("appears to be", "seems like") +- **Solution**: + - Enhanced system prompts with explicit factual reporting requirements + - Banned interpretive language in responses + - Required structured **System Facts** format + - Added examples of good vs bad responses + +### Async State Management +- **Problem**: Chat UI would hang on "..." while processing +- **Solution**: + - Replaced complex Action pattern with direct `spawn_local` + - Improved error handling in async operations + - Added proper state updates during processing + +## Build Configuration + +### Feature Flags +```toml +# Default: Local LLM only +cargo tauri dev + +# With cloud API support +cargo tauri dev --features cloud-api + +# Local LLM specifically +cargo tauri dev --features local-llm +``` + +### Dependencies +```toml +[dependencies] +# Core async runtime +tokio = { version = "1.0", features = ["full"] } + +# Local LLM (Ollama) +ollama-rs = { version = "0.2", optional = true } + +# Cloud APIs +async-openai = { version = "0.28", optional = true } + +# JSON handling +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } + +[features] +default = ["local-llm"] +local-llm = ["dep:ollama-rs"] +cloud-api = ["dep:async-openai"] +``` + +## Current Status + +### ✅ Working Features +- Chat UI with proper async handling +- Environment detection (desktop vs web) +- Automatic model context loading from JSON files +- Ollama integration with llama3.2:3b model +- OpenAI API integration (when enabled) +- Enhanced factual response generation +- Structured system analysis output +- Graceful fallback to mock responses + +### ⚠️ Known Issues & Areas for Improvement + +#### Response Quality Issues (January 2025) +**Problem**: Despite enhanced system prompts, LLM responses still contain interpretive language: +- Still uses: "appears to be", "seems like", "overall this system appears to be" +- Not following the structured **System Facts** format consistently +- Providing interpretive analysis instead of factual data extraction + +**Example Current Response**: +``` +"Based on the provided data, it appears to be a complex system for managing and maintaining the Bitcoin blockchain..." +``` + +**Target Response Format**: +``` +**System Facts:** +• **Name**: Bitcoin Network +• **Subsystems**: 4 total +• **Interactions**: 12 total +• **Components**: Protocol, Validating, Mining, Development +• **Flows**: F0.0 Protocol Rules & Parameters, F0.1 Mempool Transactions, etc. +``` + +#### Root Causes Identified: +1. **System Prompt Effectiveness**: Current prompt may not be strong enough to override LLM's natural interpretive tendencies +2. **Model Training Bias**: llama3.2:3b may be inherently trained to provide interpretive responses +3. **Context Processing**: LLM may not be properly parsing the structured format requirements +4. **Prompt Engineering**: May need more aggressive prompt techniques (few-shot examples, stronger constraints) + +#### Targeted Improvements Needed: +1. **Stronger Prompt Engineering**: + - Add few-shot examples showing exact desired vs undesired responses + - Use more aggressive language constraints + - Implement response validation/filtering + +2. **Response Post-Processing**: + - Add automatic detection of interpretive language + - Implement response rewriting to remove banned phrases + - Force structured format compliance + +3. **Alternative Model Testing**: + - Test with different local models (llama3.1, mistral, etc.) + - Compare cloud API responses (OpenAI GPT-4, Claude) + - Evaluate which models better follow structured instructions + +4. **Enhanced Context Extraction**: + - Improve JSON parsing and summarization + - Provide more explicit data structure to LLM + - Pre-format data in the exact output structure desired + +### 🔧 Performance Notes +- **2019 MacBook Pro**: 5-15 tokens/second with local LLM +- **Future Apple Silicon**: Expected 50-150+ tokens/second +- **Cloud APIs**: Instant responses but require internet + API costs + +### 📋 Response Format +The chat now provides structured, factual analysis: + +``` +**System Facts:** +• **Name**: Bitcoin Network +• **Subsystems**: 4 total +• **Interactions**: 12 total +• **Components**: Protocol, Validating, Mining, Network Distribution +• **Flows**: block_subsidy, network_difficulty, utxo_set_hash, etc. + +**Subsystem Analysis:** +[Exact subsystem names and properties from JSON] + +**Interaction Analysis:** +[Exact interaction flows with source→sink mappings] +``` + +## Usage Examples + +### Basic Chat Commands +- "What is this system?" - Get system overview +- "List all components" - Show subsystems and their properties +- "Show interactions" - Display flows between components +- "Explain the mining process" - Get Bitcoin-specific analysis + +### Model Context +The chat automatically uses the currently loaded JSON model data. When you load a `.btc`, `.json`, or other system model file, the chat context updates automatically. + +## Troubleshooting + +### Ollama Issues +- **Connection Failed**: Ensure `ollama serve` is running +- **Model Not Found**: Run `ollama pull llama3.2:3b` +- **Port Conflicts**: Check if port 11434 is available + +### Build Issues +- **Feature Conflicts**: Use only one feature flag at a time +- **Dependency Errors**: Clear `target/` and rebuild +- **Port 1320 Occupied**: Kill existing processes with `lsof -ti:1320 | xargs kill -9` + +### Chat UI Issues +- **Hanging on "..."**: Check browser console for errors +- **No Response**: Verify model context is loaded +- **Mock Responses Only**: Check if Ollama is running and model exists + +## Future Enhancements + +### Immediate Priority (Next Development Session) +1. **Fix Factual Response Issues**: + - Implement few-shot prompting with explicit good/bad examples + - Add response post-processing to filter interpretive language + - Test alternative models for better instruction following + - Strengthen system prompts with more aggressive constraints + +2. **Response Validation**: + - Automatic detection of banned phrases ("appears to be", "seems like") + - Force structured format compliance before returning responses + - Implement response quality scoring + +### Medium-Term Improvements +1. **Structured Analysis Frameworks** + - Leverage points analysis + - Stock & flow diagrams + - Systems archetypes identification + +2. **Visual Integration** + - Highlight discussed components in the visual model + - Generate system diagrams from chat insights + - Interactive exploration with guided questions + +3. **Domain-Specific Intelligence** + - Bitcoin/cryptocurrency expertise + - Systems thinking methodologies + - Comparative analysis capabilities + +4. **Performance Optimizations** + - Model caching for faster responses + - Streaming responses for real-time feedback + - Context compression for large models + +### Development Notes for Next Session +- **Current Issue**: LLM not following structured format despite enhanced prompts +- **Test Data**: Use Bitcoin model with Protocol, Validating, Mining, Development subsystems +- **Success Criteria**: Responses must start with "**System Facts:**" format and avoid all interpretive language +- **Quick Wins**: Try OpenAI API to compare response quality vs local llama3.2:3b + +## Contributing + +When working on the chat feature: + +1. **Test Both Environments**: Desktop (Tauri) and web browser +2. **Check Model Context**: Ensure loaded JSON data is properly passed +3. **Verify Response Quality**: Responses should be factual, not interpretive +4. **Handle Errors Gracefully**: Provide helpful fallbacks when LLMs are unavailable +5. **Update Documentation**: Keep this file current with any changes + +## Dependencies & Versions + +- **Leptos**: Frontend framework for reactive UI +- **Tauri**: Desktop app framework for Rust +- **Ollama**: Local LLM inference server +- **OpenAI API**: Cloud-based language model access +- **Tokio**: Async runtime for Rust +- **Serde**: JSON serialization/deserialization + +Last Updated: January 2025 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b871bb2..f0fe8b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,6 +217,28 @@ dependencies = [ "libc", ] +[[package]] +name = "anthropic" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092086afb8afa8a2bf24820213e22a57662eace03bd6b3c2680f6baf022ef6ba" +dependencies = [ + "backoff", + "config 0.13.4", + "derive_builder 0.12.0", + "lazy_static", + "log", + "reqwest 0.11.27", + "reqwest-eventsource 0.4.0", + "rustc_version", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", +] + [[package]] name = "any_spawner" version = "0.2.0" @@ -421,6 +443,43 @@ dependencies = [ "futures-lite", ] +[[package]] +name = "async-openai" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df839a6643e1e3248733b01f229dc4f462d7256f808bbaf04cac40739b345c2" +dependencies = [ + "async-openai-macros", + "backoff", + "base64 0.22.1", + "bytes", + "derive_builder 0.20.2", + "eventsource-stream", + "futures", + "rand 0.8.5", + "reqwest 0.12.12", + "reqwest-eventsource 0.6.0", + "secrecy", + "serde", + "serde_json", + "thiserror 2.0.11", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "async-openai-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0289cba6d5143bfe8251d57b4a8cac036adf158525a76533a7082ba65ec76398" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "async-process" version = "2.3.0" @@ -469,6 +528,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "async-task" version = "4.7.1" @@ -557,6 +638,20 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "futures-core", + "getrandom 0.2.15", + "instant", + "pin-project-lite", + "rand 0.8.5", + "tokio", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -572,6 +667,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -588,7 +689,13 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" name = "bert" version = "0.1.0" dependencies = [ + "anthropic", + "anyhow", + "async-openai", + "dirs 5.0.1", "leptos", + "ollama-rs", + "reqwest 0.12.12", "serde", "serde_json", "tauri", @@ -596,6 +703,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-fs", "tauri-plugin-opener", + "tokio", ] [[package]] @@ -691,7 +799,7 @@ dependencies = [ "downcast-rs", "either", "petgraph", - "ron", + "ron 0.8.1", "serde", "smallvec", "thread_local", @@ -745,7 +853,7 @@ dependencies = [ "futures-lite", "js-sys", "parking_lot", - "ron", + "ron 0.8.1", "serde", "stackfuture", "uuid", @@ -2093,7 +2201,7 @@ dependencies = [ "cocoa-foundation", "core-foundation 0.10.0", "core-graphics 0.24.0", - "foreign-types", + "foreign-types 0.5.0", "libc", "objc", ] @@ -2167,6 +2275,20 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" +dependencies = [ + "async-trait", + "lazy_static", + "nom", + "pathdiff", + "ron 0.7.1", + "serde", +] + [[package]] name = "config" version = "0.14.1" @@ -2330,7 +2452,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -2343,7 +2465,7 @@ dependencies = [ "bitflags 2.8.0", "core-foundation 0.10.0", "core-graphics-types 0.2.0", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -2556,14 +2678,38 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] @@ -2576,17 +2722,28 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.98", ] +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core", + "darling_core 0.20.10", "quote", "syn 2.0.98", ] @@ -2656,7 +2813,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0df63c21a4383f94bd5388564829423f35c316aed85dc4f8427aded372c7c0d" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2", "quote", "syn 2.0.98", @@ -2683,6 +2840,68 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro 0.12.0", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro 0.20.2", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core 0.12.0", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core 0.20.2", + "syn 2.0.98", +] + [[package]] name = "derive_more" version = "0.99.19" @@ -2940,7 +3159,7 @@ dependencies = [ "rustc_version", "toml 0.8.20", "vswhom", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -2981,6 +3200,15 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -3119,6 +3347,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -3216,6 +3455,15 @@ dependencies = [ "ttf-parser 0.20.0", ] +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -3223,7 +3471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -3237,6 +3485,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -3353,6 +3607,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -3723,7 +3983,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http", + "http 1.2.0", "js-sys", "pin-project", "serde", @@ -3952,6 +4212,44 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.7.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.2.0", + "indexmap 2.7.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -4051,6 +4349,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.14", +] + [[package]] name = "http" version = "1.2.0" @@ -4062,6 +4371,17 @@ dependencies = [ "itoa 1.0.14", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -4069,7 +4389,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.2.0", ] [[package]] @@ -4080,8 +4400,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http", - "http-body", + "http 1.2.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -4091,6 +4411,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hydration_context" version = "0.2.1" @@ -4105,6 +4431,30 @@ dependencies = [ "throw_error", ] +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa 1.0.14", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.6.0" @@ -4114,8 +4464,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", + "h2 0.4.10", + "http 1.2.0", + "http-body 1.0.1", "httparse", "itoa 1.0.14", "pin-project-lite", @@ -4124,6 +4475,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.5" @@ -4131,17 +4496,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.2.0", + "hyper 1.6.0", "hyper-util", - "rustls", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.1", "tower-service", "webpki-roots", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -4151,9 +4533,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", - "http-body", - "hyper", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", "pin-project-lite", "socket2", "tokio", @@ -4418,6 +4800,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "interpolator" version = "0.5.0" @@ -4717,7 +5108,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4172cfee12576224775ccfbb9d3e76625017a8b4207c4641a2f9b96a70e6d524" dependencies = [ - "config", + "config 0.14.1", "regex", "serde", "thiserror 2.0.11", @@ -5094,7 +5485,7 @@ dependencies = [ "bitflags 2.8.0", "block", "core-graphics-types 0.1.3", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", @@ -5106,6 +5497,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -5195,6 +5596,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.8.0" @@ -5712,30 +6130,91 @@ dependencies = [ ] [[package]] -name = "ogg" -version = "0.8.0" +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "ollama-rs" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5df54edb7e1264719be607cd40590d3769b5b35a2623e6e02681e6591aea5b8" +dependencies = [ + "async-stream", + "log", + "reqwest 0.12.12", + "schemars", + "serde", + "serde_json", + "static_assertions", + "thiserror 2.0.11", + "url", +] + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "open" +version = "5.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "byteorder", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "once_cell" -version = "1.20.3" +name = "openssl-probe" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] -name = "open" -version = "5.3.2" +name = "openssl-sys" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ - "dunce", - "is-wsl", + "cc", "libc", - "pathdiff", + "pkg-config", + "vcpkg", ] [[package]] @@ -6312,7 +6791,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls", + "rustls 0.23.23", "socket2", "thiserror 2.0.11", "tokio", @@ -6330,7 +6809,7 @@ dependencies = [ "rand 0.8.5", "ring", "rustc-hash 2.1.1", - "rustls", + "rustls 0.23.23", "rustls-pki-types", "slab", "thiserror 2.0.11", @@ -6689,6 +7168,49 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg 0.50.0", +] + [[package]] name = "reqwest" version = "0.12.12" @@ -6697,31 +7219,39 @@ checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", "futures-util", - "http", - "http-body", + "h2 0.4.10", + "http 1.2.0", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls", - "rustls-pemfile", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", + "system-configuration 0.6.1", "tokio", - "tokio-rustls", + "tokio-native-tls", + "tokio-rustls 0.26.1", "tokio-util", "tower", "tower-service", @@ -6734,6 +7264,38 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "reqwest-eventsource" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f03f570355882dd8d15acc3a313841e6e90eddbc76a93c748fd82cc13ba9f51" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest 0.11.27", + "thiserror 1.0.69", +] + +[[package]] +name = "reqwest-eventsource" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632c55746dbb44275691640e7b40c907c16a2dc1a5842aa98aaec90da6ec6bde" +dependencies = [ + "eventsource-stream", + "futures-core", + "futures-timer", + "mime", + "nom", + "pin-project-lite", + "reqwest 0.12.12", + "thiserror 1.0.69", +] + [[package]] name = "rfd" version = "0.15.2" @@ -6815,6 +7377,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64 0.13.1", + "bitflags 1.3.2", + "serde", +] + [[package]] name = "ron" version = "0.8.1" @@ -6914,6 +7487,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.23" @@ -6923,11 +7508,44 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -6946,6 +7564,16 @@ dependencies = [ "web-time", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -7004,6 +7632,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "schemars" version = "0.8.22" @@ -7043,6 +7680,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sctk-adwaita" version = "0.10.1" @@ -7062,6 +7709,52 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.8.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.8.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -7238,7 +7931,7 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ - "darling", + "darling 0.20.10", "proc-macro2", "quote", "syn 2.0.98", @@ -7277,7 +7970,7 @@ dependencies = [ "dashmap", "futures", "gloo-net", - "http", + "http 1.2.0", "js-sys", "once_cell", "pin-project-lite", @@ -7476,7 +8169,7 @@ dependencies = [ "bytemuck", "cfg_aliases 0.2.1", "core-graphics 0.24.0", - "foreign-types", + "foreign-types 0.5.0", "js-sys", "log", "objc2 0.5.2", @@ -7573,6 +8266,12 @@ dependencies = [ "quote", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -7657,6 +8356,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -7699,6 +8404,48 @@ dependencies = [ "windows 0.57.0", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.8.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -7836,7 +8583,7 @@ dependencies = [ "glob", "gtk", "heck 0.5.0", - "http", + "http 1.2.0", "jni", "libc", "log", @@ -7848,7 +8595,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest", + "reqwest 0.12.12", "serde", "serde_json", "serde_repr", @@ -8021,7 +8768,7 @@ checksum = "2274ef891ccc0a8d318deffa9d70053f947664d12d58b9c0d1ae5e89237e01f7" dependencies = [ "dpi", "gtk", - "http", + "http 1.2.0", "jni", "raw-window-handle", "serde", @@ -8039,7 +8786,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3707b40711d3b9f6519150869e358ffbde7c57567fb9b5a8b51150606939b2a0" dependencies = [ "gtk", - "http", + "http 1.2.0", "jni", "log", "objc2 0.5.2", @@ -8087,7 +8834,7 @@ dependencies = [ "dunce", "glob", "html5ever", - "http", + "http 1.2.0", "infer", "json-patch", "kuchikiki", @@ -8332,20 +9079,64 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", + "tokio-macros", "tracing", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls", + "rustls 0.23.23", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", "tokio", ] @@ -8441,7 +9232,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -8704,6 +9495,12 @@ dependencies = [ "unic-common", ] +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -8840,6 +9637,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -9960,6 +10763,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.52.0" @@ -10006,7 +10819,7 @@ dependencies = [ "gdkx11", "gtk", "html5ever", - "http", + "http 1.2.0", "javascriptcore-rs", "jni", "kuchikiki", diff --git a/commit_message.txt b/commit_message.txt new file mode 100644 index 0000000..13aba7c --- /dev/null +++ b/commit_message.txt @@ -0,0 +1,5 @@ +feat: Implement LLM chat integration with Ollama and OpenAI support + +Add complete chat UI with environment detection, MinimalLLMService with multiple providers (Ollama/OpenAI/Mock), automatic model context loading from JSON files, Tauri command integration, enhanced factual response system, comprehensive documentation, graceful fallback handling, and targeted improvement roadmap. + +Core: chat.rs, chat_service.rs, CHAT_SETUP.md | Deps: ollama-rs, async-openai, tokio | Features: local-llm, cloud-api \ No newline at end of file diff --git a/docs/development/llm-assisted-development-guide.md b/docs/development/llm-assisted-development-guide.md new file mode 100644 index 0000000..6bd224f --- /dev/null +++ b/docs/development/llm-assisted-development-guide.md @@ -0,0 +1,204 @@ +# LLM-Assisted Development Guide for BERT + +## Executive Summary + +This guide synthesizes proven patterns for accelerating feature development in the BERT project using LLM assistance. Based on successful implementations of complex features like controls menus and theme toggles, these practices enable **3-5x faster development** while maintaining **high code quality** and **architectural consistency**. + +## Core Principles + +### 1. Constraint-Driven Development +**Well-structured constraints enable faster, higher-quality development.** + +- **Cursor Rules as Architecture**: Your `.mdc` files serve as living architecture documentation that both humans and AI can follow +- **Patterns Over Invention**: Established patterns eliminate decision fatigue and ensure consistency +- **Guardrails, Not Restrictions**: Constraints channel creativity productively rather than limiting it + +### 2. Incremental Validation +**Test early, test often, fail fast.** + +- **Compilation-Driven Development**: Test compilation after each significant change +- **Phase-Gate Approach**: Complete each phase before moving to the next +- **Error-Driven Learning**: Use compilation errors as immediate feedback loops + +### 3. Documentation as Code +**Your cursor rules effectively become your system architecture.** + +- **Self-Documenting Patterns**: Consistent naming and structure reduce cognitive load +- **API as Documentation**: Well-designed component interfaces serve as usage guides +- **Living Standards**: Cursor rules evolve with the codebase + +## The Proven Workflow + +### Phase 1: Discovery & Planning (10-15 minutes) +``` +1. Search existing patterns in codebase +2. Identify integration points and file modifications +3. Propose specific technical approach +4. Get user confirmation before proceeding +``` + +**Key Success Factor**: Never start coding without understanding existing patterns. + +### Phase 2: Incremental Implementation (60-80% of time) +``` +1. Create/modify one file at a time +2. Test compilation after each change +3. Follow established patterns religiously +4. Fix errors immediately before proceeding +``` + +**Key Success Factor**: Maintain working state at all times. + +### Phase 3: Integration & Polish (15-25% of time) +``` +1. Apply consistent styling and interactions +2. Add proper documentation +3. Test complete user workflow +4. Prepare clean commit +``` + +**Key Success Factor**: Don't skip the polish—it's what makes features feel integrated. + +## Critical Success Patterns + +### Pattern 1: Modal Component Implementation +```rust +// Standard modal pattern that works every time +#[component] +pub fn MyModal( + #[prop(into)] visible: Signal, + #[prop(into)] on_close: Callback<()>, +) -> impl IntoView { + view! { + +
+
+ // Content here +
+
+
+ } +} +``` + +### Pattern 2: Component Integration +```rust +// In App component - signal management +let (feature_visible, set_feature_visible) = signal(false); + +// Component usage with proper callback + +``` + +### Pattern 3: File Structure +``` +1. Create: src/leptos_app/components/my_component.rs +2. Export: Add to src/leptos_app/components/mod.rs +3. Import: Use in src/leptos_app/mod.rs +4. Integrate: Add to App component +``` + +## Common Pitfalls and Solutions + +### Pitfall 1: API Version Mismatches +**Problem**: Using outdated API patterns (e.g., Leptos callback syntax) +**Solution**: Check version-specific documentation and test compilation frequently + +### Pitfall 2: Pattern Deviation +**Problem**: Inventing new patterns instead of following established ones +**Solution**: Always research existing implementations first + +### Pitfall 3: Integration Complexity +**Problem**: Underestimating how components integrate with existing systems +**Solution**: Map integration points before implementation + +## Quality Metrics + +### High-Quality Implementation Indicators +- ✅ **Compilation**: No errors, minimal warnings +- ✅ **Integration**: Seamless with existing features +- ✅ **Consistency**: Follows established patterns +- ✅ **Performance**: No noticeable impact +- ✅ **Maintainability**: Well-documented and extensible + +### Process Efficiency Indicators +- ✅ **Speed**: Minimal back-and-forth during implementation +- ✅ **Accuracy**: No major rework required +- ✅ **Reusability**: Patterns documented for future use +- ✅ **Learning**: Insights captured for continuous improvement + +## Technology-Specific Considerations + +### Leptos (Frontend Framework) +- **Signals**: Use `Signal` for reactive props, `move ||` for reactive reads +- **Callbacks**: Use `.run()` method in Leptos 0.7+ +- **Components**: Follow `#[component]` and `#[prop(into)]` patterns + +### Bevy (Game Engine) +- **Entities**: Use established query patterns and component bundles +- **Resources**: Ensure proper initialization and access patterns +- **Systems**: Follow existing system organization and event handling + +### Tauri (Desktop Framework) +- **Build Process**: Be aware of frontend/backend compilation dependencies +- **Port Conflicts**: Kill existing processes when encountering port issues +- **Development**: Use `cargo tauri dev` for integrated development experience + +## Optimization Strategies + +### For Simple Features (< 2 hours) +- Combine discovery and implementation phases +- Focus on pattern reuse over customization +- Minimal documentation beyond code comments + +### For Complex Features (> 4 hours) +- Add intermediate checkpoints within phases +- Create detailed implementation plan +- Consider breaking into smaller features + +### For Team Development +- Document new patterns immediately +- Share successful implementations as templates +- Maintain pattern library for common use cases + +## Measuring Success + +### Development Velocity +- **Target**: 3-5x faster implementation than traditional development +- **Measure**: Time from requirement to working feature +- **Optimize**: Reduce discovery and debugging time + +### Code Quality +- **Target**: Zero regressions, minimal technical debt +- **Measure**: Compilation success rate, integration smoothness +- **Optimize**: Improve pattern documentation and error prevention + +### Knowledge Transfer +- **Target**: Patterns reusable by other developers +- **Measure**: Documentation completeness, pattern adoption +- **Optimize**: Capture and share successful implementations + +## Future Evolution + +### Continuous Improvement +- **Pattern Documentation**: Capture new successful patterns immediately +- **Error Prevention**: Document common mistakes and solutions +- **Process Refinement**: Evolve workflow based on experience + +### Scaling Considerations +- **Team Adoption**: Train team members on cursor rules and patterns +- **Pattern Library**: Build comprehensive library of reusable components +- **Automation**: Consider automating repetitive pattern implementations + +## Conclusion + +LLM-assisted development with well-structured cursor rules transforms feature implementation from a time-consuming, error-prone process into a fast, predictable, and high-quality workflow. The key is treating cursor rules not as restrictions, but as **architectural guardrails** that enable both human and AI decision-making to converge on consistent, maintainable solutions. + +**The result**: Features that feel like they've always been part of the application, implemented in a fraction of the traditional time, with quality that meets or exceeds hand-crafted code. + +--- + +*This guide is a living document. Update it as new patterns emerge and the development process evolves.* \ No newline at end of file diff --git a/docs/roadmap-template.md b/docs/roadmap-template.md new file mode 100644 index 0000000..902f663 --- /dev/null +++ b/docs/roadmap-template.md @@ -0,0 +1,141 @@ +# 🗺️ BERT Development Roadmap + +## 📋 Overview + +This document outlines the strategic development plan for BERT, prioritizing features that enhance the system modeling experience and support research objectives. + +--- + +## 🔍 Priority Framework + +Features are categorized into priority levels based on their impact, complexity, and alignment with core objectives: + +| Priority | Description | Timeline | +|:--------:|-------------|----------| +| 🔴 P0 | **Critical Path** - Core functionality essential for basic product usability | Immediate (0-3 months) | +| 🟠 P1 | **High Impact** - Features with significant value for most users | Short-term (3-6 months) | +| 🟡 P2 | **Important** - Features that enhance the product but aren't critical | Medium-term (6-12 months) | +| 🟢 P3 | **Nice to Have** - Valuable enhancements for specific use cases | Long-term (12+ months) | + +--- + +## 🚀 Development Streams + +Features are organized into focused development streams to enable parallel implementation: + +### 🎨 User Experience +Features focused on improving interaction design, accessibility, and visual aesthetics. + +#### P0 (Critical) +- `[Feature template]` + - Description: Brief outline of the feature + - Success criteria: How we'll know it's complete + - Implementation notes: Technical considerations + +#### P1 (High) +- `[Feature template]` + +#### P2 (Important) +- `[Feature template]` + +--- + +### 🔧 Core Functionality +Features that enhance the fundamental capabilities of the system modeling engine. + +#### P0 (Critical) +- `[Feature template]` + +#### P1 (High) +- `[Feature template]` + +--- + +### 📊 Analysis & Visualization +Features that improve how systems are analyzed, visualized, and interpreted. + +#### P1 (High) +- `[Feature template]` + +#### P2 (Important) +- `[Feature template]` + +--- + +### 🔄 Integration & Interoperability +Features that connect BERT with external tools, data sources, and workflows. + +#### P2 (Important) +- `[Feature template]` + +#### P3 (Nice to Have) +- `[Feature template]` + +--- + +### 📚 Documentation & Learning +Features that improve documentation, tutorials, and knowledge sharing. + +#### P1 (High) +- `[Feature template]` + +#### P2 (Important) +- `[Feature template]` + +--- + +## 📈 Release Strategy + +### Milestone 1: Foundation (v0.x) +Focus on core modeling capabilities and essential user experience. + +### Milestone 2: Expansion (v1.x) +Enhance analysis capabilities and initial integration features. + +### Milestone 3: Ecosystem (v2.x) +Build out the broader ecosystem and specialized capabilities. + +--- + +## 🔄 Review Process + +This roadmap is a living document and will be reviewed: +- Monthly for tactical adjustments +- Quarterly for strategic alignment +- After major releases for retrospective insights + +--- + +## 📝 Feature Request Template + +```markdown +## Feature: [Name] + +### Description +[Brief description of the feature] + +### User Story +As a [type of user], I want to [action] so that [benefit]. + +### Success Criteria +- [Measurable outcome 1] +- [Measurable outcome 2] + +### Technical Considerations +- [Implementation note 1] +- [Implementation note 2] + +### Priority +[P0/P1/P2/P3] - [Justification] + +### Dependencies +- [Dependency 1] +- [Dependency 2] + +### Estimated Effort +[Small/Medium/Large] - [Justification] +``` + +--- + +*Last updated: [Date]* \ No newline at end of file diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 86a11f8..223b04f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,11 +18,28 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tauri-plugin-dialog = "2" -tauri-plugin-fs = "2" +tauri = { version = "2.2", features = [] } +tauri-plugin-fs = "2.2" +tauri-plugin-dialog = "2.2" +tauri-plugin-opener = "2.2" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0" +dirs = "5.0" +tokio = { version = "1.0", features = ["full"] } + +# Minimal LLM chat dependencies leptos = { version = "0.7", features = ["csr"] } +# LLM Providers (all optional, but ollama is default) +ollama-rs = { version = "0.2", optional = true } +reqwest = { version = "0.12", features = ["json"], optional = true } +async-openai = { version = "0.28", optional = true } +anthropic = { version = "0.0.8", optional = true } + +[features] +default = ["local-llm"] +local-llm = ["ollama-rs"] +cloud-api = ["reqwest", "async-openai", "anthropic"] +all-providers = ["local-llm", "cloud-api"] + diff --git a/src-tauri/src/chat_service.rs b/src-tauri/src/chat_service.rs new file mode 100644 index 0000000..040b0e0 --- /dev/null +++ b/src-tauri/src/chat_service.rs @@ -0,0 +1,526 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[cfg(feature = "local-llm")] +use ollama_rs::{ + generation::{ + chat::{request::ChatMessageRequest, ChatMessage, MessageRole}, + }, + Ollama, +}; + +#[cfg(feature = "cloud-api")] +use async_openai::{Client as OpenAIClient, types::*}; + +// #[cfg(feature = "cloud-api")] +// use anthropic::{Client as AnthropicClient, types::*}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChatRequest { + pub message: String, + pub model_context: String, // Serialized JSON model +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ChatResponse { + pub response: String, + pub error: Option, +} + +#[derive(Debug, Clone)] +pub enum LLMProvider { + #[cfg(feature = "local-llm")] + Local(Ollama, String), // (ollama_client, model_name) + // #[cfg(feature = "cloud-api")] + // Claude(AnthropicClient, String), // (claude_client, model_name) + #[cfg(feature = "cloud-api")] + OpenAI(OpenAIClient, String), // (openai_client, model_name) + Mock, // Fallback when no providers available +} + +#[derive(Clone)] +pub struct MinimalLLMService { + model_path: Option, + provider: LLMProvider, +} + +impl MinimalLLMService { + pub fn new() -> Result { + // Provider priority: 1) Local Ollama (default), 2) Claude, 3) OpenAI, 4) Mock + let provider = Self::detect_best_provider(); + + Ok(Self { + model_path: None, + provider, + }) + } + + fn detect_best_provider() -> LLMProvider { + // First try local Ollama - this is the default and preferred option + #[cfg(feature = "local-llm")] + { + // Always try local first - this preserves existing behavior + let ollama = Ollama::new("http://localhost", 11434); + return LLMProvider::Local(ollama, "llama3.2:3b".to_string()); + } + + // If local not available, check for cloud API keys + #[cfg(feature = "cloud-api")] + { + // For now, use OpenAI as the primary cloud option + if let Ok(api_key) = std::env::var("OPENAI_API_KEY") { + if !api_key.is_empty() { + let client = OpenAIClient::new(); + return LLMProvider::OpenAI(client, "gpt-4o-mini".to_string()); + } + } + + // Claude support temporarily disabled due to API version compatibility + // TODO: Re-enable once anthropic crate API is updated + // if let Ok(api_key) = std::env::var("ANTHROPIC_API_KEY") { + // if !api_key.is_empty() { + // let client = AnthropicClient::new(api_key); + // return LLMProvider::Claude(client, "claude-3-5-haiku-20241022".to_string()); + // } + // } + } + + // Final fallback to mock responses + LLMProvider::Mock + } + + pub async fn initialize_model(&mut self, model_name: &str) -> Result<()> { + match &mut self.provider { + #[cfg(feature = "local-llm")] + LLMProvider::Local(_, current_model) => { + *current_model = model_name.to_string(); + } + // #[cfg(feature = "cloud-api")] + // LLMProvider::Claude(_, current_model) => { + // *current_model = model_name.to_string(); + // } + #[cfg(feature = "cloud-api")] + LLMProvider::OpenAI(_, current_model) => { + *current_model = model_name.to_string(); + } + LLMProvider::Mock => { + // Mock provider doesn't need model initialization + } + } + Ok(()) + } + + pub async fn chat(&self, request: ChatRequest) -> Result { + match self.generate_llm_response(&request.message, &request.model_context).await { + Ok(response) => Ok(ChatResponse { + response, + error: None, + }), + Err(e) => { + let enhanced_response = Self::create_enhanced_mock_response(&request.message, &request.model_context); + let provider_info = match &self.provider { + #[cfg(feature = "local-llm")] + LLMProvider::Local(_, _) => "Local Ollama unavailable. Install with: `curl -fsSL https://ollama.ai/install.sh | sh && ollama pull llama3.2:3b`", + // #[cfg(feature = "cloud-api")] + // LLMProvider::Claude(_, _) => "Claude API error. Check your ANTHROPIC_API_KEY", + #[cfg(feature = "cloud-api")] + LLMProvider::OpenAI(_, _) => "OpenAI API error. Check your OPENAI_API_KEY", + LLMProvider::Mock => "Using mock responses", + }; + + Ok(ChatResponse { + response: format!("{}\n\n⚠️ **{}**: {}", enhanced_response, provider_info, e), + error: Some(e.to_string()), + }) + } + } + } + + async fn generate_llm_response(&self, question: &str, model_context: &str) -> Result { + match &self.provider { + #[cfg(feature = "local-llm")] + LLMProvider::Local(ollama, model_name) => { + self.chat_with_ollama(question, model_context, ollama, model_name).await + } + // #[cfg(feature = "cloud-api")] + // LLMProvider::Claude(client, model_name) => { + // self.chat_with_claude(question, model_context, client, model_name).await + // } + #[cfg(feature = "cloud-api")] + LLMProvider::OpenAI(client, model_name) => { + self.chat_with_openai(question, model_context, client, model_name).await + } + LLMProvider::Mock => { + Ok(Self::create_enhanced_mock_response(question, model_context)) + } + } + } + + #[cfg(feature = "local-llm")] + async fn chat_with_ollama(&self, question: &str, model_context: &str, ollama: &Ollama, model_name: &str) -> Result { + let context_summary = self.extract_model_summary(model_context); + + let system_prompt = self.get_system_prompt(); + let user_prompt = format!( + "Analyze this BERT system model. Model context: {}\n\nUser question: {}", + context_summary, question + ); + + let messages = vec![ + ChatMessage::new(MessageRole::System, system_prompt), + ChatMessage::new(MessageRole::User, user_prompt), + ]; + + let chat_request = ChatMessageRequest::new(model_name.to_string(), messages); + let response = ollama.send_chat_messages(chat_request).await?; + + Ok(response.message.content) + } + + // #[cfg(feature = "cloud-api")] + // async fn chat_with_claude(&self, question: &str, model_context: &str, client: &AnthropicClient, model_name: &str) -> Result { + // let context_summary = self.extract_model_summary(model_context); + // let system_prompt = self.get_system_prompt(); + // let user_prompt = format!( + // "Analyze this BERT system model. Model context: {}\n\nUser question: {}", + // context_summary, question + // ); + + // let request = CreateMessageRequestArgs::default() + // .model(model_name) + // .system(system_prompt) + // .messages(vec![ + // MessageArgs::default() + // .role(Role::User) + // .content(user_prompt) + // .build()? + // ]) + // .max_tokens(1000) + // .build()?; + + // let response = client.messages().create(request).await?; + + // // Extract text content from response + // if let Some(content) = response.content.first() { + // if let Content::Text { text } = content { + // Ok(text.clone()) + // } else { + // Ok("No text content in response".to_string()) + // } + // } else { + // Ok("Empty response from Claude".to_string()) + // } + // } + + #[cfg(feature = "cloud-api")] + async fn chat_with_openai(&self, question: &str, model_context: &str, client: &OpenAIClient, model_name: &str) -> Result { + let context_summary = self.extract_model_summary(model_context); + let system_prompt = self.get_system_prompt(); + let user_prompt = format!( + "Analyze this BERT system model. Model context: {}\n\nUser question: {}", + context_summary, question + ); + + let request = CreateChatCompletionRequestArgs::default() + .model(model_name) + .messages([ + ChatCompletionRequestSystemMessageArgs::default() + .content(system_prompt) + .build()? + .into(), + ChatCompletionRequestUserMessageArgs::default() + .content(user_prompt) + .build()? + .into(), + ]) + .build()?; + + let response = client.chat().completions().create(request).await?; + + Ok(response.choices[0].message.content.clone().unwrap_or_default()) + } + + fn get_system_prompt(&self) -> String { + r#"You are a BERT systems analysis assistant. Report FACTS from JSON data ONLY. + +FORBIDDEN WORDS/PHRASES (never use these): +- "appears to be", "seems", "suggests", "likely", "probably", "may be" +- "this system seems to...", "appears to handle...", "looks like..." + +REQUIRED FORMAT - Start ALL responses with: +**System Facts:** +• **Name**: [exact string from JSON "name" field] +• **Subsystems**: [exact count] total +• **Interactions**: [exact count] total +• **Components**: [list exact names from JSON] +• **Flows**: [list exact interaction names/types] + +THEN provide factual breakdown: +**Subsystem Analysis:** +[List each subsystem name exactly as written in JSON, with its exact properties] + +**Interaction Analysis:** +[List each interaction name exactly as written, with exact source→sink pairs] + +RULES: +1. Extract data EXACTLY as written in JSON +2. Count elements precisely +3. Quote field names and values directly +4. State what IS present, not what it might mean +5. If unsure about a fact, state "Data not available" instead of guessing + +Example BAD response: "This appears to be a Bitcoin system that seems to handle..." +Example GOOD response: "This system contains 4 subsystems named: Protocol, Validating, Mining, Network Distribution. The system has 12 interactions including..." + +Report the DATA, don't interpret its meaning."#.to_string() + } + + fn extract_model_summary(&self, model_context: &str) -> String { + // Parse JSON and extract key information for the LLM + if let Ok(model_data) = serde_json::from_str::(model_context) { + let mut summary = String::new(); + + // Extract basic model info + if let Some(env) = model_data.get("environment") { + if let Some(info) = env.get("info") { + if let Some(name) = info.get("name").and_then(|n| n.as_str()) { + summary.push_str(&format!("Model Name: {}\n", name)); + } + if let Some(description) = info.get("description").and_then(|d| d.as_str()) { + summary.push_str(&format!("Description: {}\n", description)); + } + } + + // Extract systems information + if let Some(systems) = env.get("systems").and_then(|s| s.as_array()) { + summary.push_str(&format!("\nSystems ({}): \n", systems.len())); + for (i, system) in systems.iter().take(5).enumerate() { // Limit to first 5 for context size + if let Some(name) = system.get("name").and_then(|n| n.as_str()) { + summary.push_str(&format!(" {}. {}", i + 1, name)); + if let Some(desc) = system.get("description").and_then(|d| d.as_str()) { + summary.push_str(&format!(" - {}", desc)); + } + summary.push('\n'); + } + } + if systems.len() > 5 { + summary.push_str(&format!(" ... and {} more systems\n", systems.len() - 5)); + } + } + + // Extract interactions information + if let Some(interactions) = env.get("interactions").and_then(|i| i.as_array()) { + summary.push_str(&format!("\nInteractions ({}): \n", interactions.len())); + for (i, interaction) in interactions.iter().take(3).enumerate() { // Limit to first 3 + if let Some(name) = interaction.get("name").and_then(|n| n.as_str()) { + summary.push_str(&format!(" {}. {}", i + 1, name)); + if let Some(source) = interaction.get("source").and_then(|s| s.as_str()) { + if let Some(sink) = interaction.get("sink").and_then(|s| s.as_str()) { + summary.push_str(&format!(" (from {} to {})", source, sink)); + } + } + summary.push('\n'); + } + } + if interactions.len() > 3 { + summary.push_str(&format!(" ... and {} more interactions\n", interactions.len() - 3)); + } + } + } + + // Provide the raw JSON as well for complex queries + summary.push_str(&format!("\nFull Model Data:\n{}", model_context)); + summary + } else { + format!("Model data: {}", model_context) + } + } + + fn create_enhanced_mock_response(message: &str, context: &str) -> String { + // Parse the JSON context to extract EXACT information + let model_info = if let Ok(json) = serde_json::from_str::(context) { + let system_name = json.get("environment") + .and_then(|env| env.get("info")) + .and_then(|info| info.get("name")) + .and_then(|v| v.as_str()) + .unwrap_or("Unknown System"); + + let description = json.get("environment") + .and_then(|env| env.get("info")) + .and_then(|info| info.get("description")) + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let systems_count = json.get("systems") + .and_then(|v| v.as_array()) + .map(|arr| arr.len()) + .unwrap_or(0); + + let interactions_count = json.get("interactions") + .and_then(|v| v.as_array()) + .map(|arr| arr.len()) + .unwrap_or(0); + + // Extract exact source names + let sources: Vec = json.get("environment") + .and_then(|env| env.get("sources")) + .and_then(|sources| sources.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|item| item.get("info")?.get("name")?.as_str()) + .map(|s| s.to_string()) + .collect() + }) + .unwrap_or_default(); + + // Extract exact sink names + let sinks: Vec = json.get("environment") + .and_then(|env| env.get("sinks")) + .and_then(|sinks| sinks.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|item| item.get("info")?.get("name")?.as_str()) + .map(|s| s.to_string()) + .collect() + }) + .unwrap_or_default(); + + // Extract exact subsystem names + let subsystems: Vec = json.get("systems") + .and_then(|systems| systems.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|item| item.get("info")?.get("name")?.as_str()) + .map(|s| s.to_string()) + .collect() + }) + .unwrap_or_default(); + + // Extract exact interaction names and details + let interactions: Vec = json.get("interactions") + .and_then(|interactions| interactions.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|item| { + let name = item.get("info")?.get("name")?.as_str()?; + let from = item.get("source")?.as_str().unwrap_or("Unknown"); + let to = item.get("sink")?.as_str().unwrap_or("Unknown"); + Some(format!("{} (from {} to {})", name, from, to)) + }) + .collect() + }) + .unwrap_or_default(); + + // STRUCTURED FACTUAL FORMAT (exactly what we want from real LLM) + let sources_str = if sources.is_empty() { "None found".to_string() } else { sources.join(", ") }; + let sinks_str = if sinks.is_empty() { "None found".to_string() } else { sinks.join(", ") }; + let subsystems_str = if subsystems.is_empty() { "None found".to_string() } else { subsystems.join(", ") }; + let interactions_str = if interactions.is_empty() { "None found".to_string() } else { interactions.join(", ") }; + + format!( + "**System Facts:**\n\ + • **System Name**: {}\n\ + • **Description**: {}\n\ + • **Subsystem Count**: {}\n\ + • **Interaction Count**: {}\n\ + • **Sources**: {}\n\ + • **Sinks**: {}\n\ + • **Subsystems**: {}\n\ + • **Interactions**: {}", + system_name, + if description.is_empty() { "Not specified" } else { description }, + systems_count, + interactions_count, + sources_str, + sinks_str, + subsystems_str, + interactions_str + ) + } else { + "**System Facts**: Unable to parse model data - please load a valid JSON model file.".to_string() + }; + + let message_lower = message.to_lowercase(); + + // Provide FACTUAL responses based on exact data + if message_lower.contains("what is") && (message_lower.contains("system") || message_lower.contains("this")) { + format!("{}\n\nThese facts are extracted directly from your loaded JSON model data.", model_info) + } else if message_lower.contains("sources") || message_lower.contains("source") { + let sources_data = if let Ok(json) = serde_json::from_str::(context) { + json.get("environment") + .and_then(|env| env.get("sources")) + .and_then(|sources| sources.as_array()) + .map(|arr| { + if arr.is_empty() { + "No sources found in the model data.".to_string() + } else { + let source_list: Vec = arr.iter() + .filter_map(|item| item.get("info")?.get("name")?.as_str()) + .map(|s| format!("• {}", s)) + .collect(); + format!("**Sources in the model:**\n{}", source_list.join("\n")) + } + }) + .unwrap_or("No sources section found in model data.".to_string()) + } else { + "Unable to parse model data.".to_string() + }; + format!("{}\n\n{}", model_info, sources_data) + } else if message_lower.contains("components") || message_lower.contains("subsystems") { + let components_data = if let Ok(json) = serde_json::from_str::(context) { + json.get("systems") + .and_then(|systems| systems.as_array()) + .map(|arr| { + if arr.is_empty() { + "No subsystems found in the model data.".to_string() + } else { + let component_list: Vec = arr.iter() + .filter_map(|item| item.get("info")?.get("name")?.as_str()) + .enumerate() + .map(|(i, s)| format!("{}. {}", i + 1, s)) + .collect(); + format!("**Subsystems in the model:**\n{}", component_list.join("\n")) + } + }) + .unwrap_or("No systems section found in model data.".to_string()) + } else { + "Unable to parse model data.".to_string() + }; + format!("{}\n\n{}", model_info, components_data) + } else if message_lower.contains("interactions") || message_lower.contains("flows") { + let interactions_data = if let Ok(json) = serde_json::from_str::(context) { + json.get("interactions") + .and_then(|interactions| interactions.as_array()) + .map(|arr| { + if arr.is_empty() { + "No interactions found in the model data.".to_string() + } else { + let interaction_list: Vec = arr.iter() + .enumerate() + .filter_map(|(i, item)| { + let name = item.get("info")?.get("name")?.as_str()?; + let from = item.get("source")?.as_str().unwrap_or("Unknown source"); + let to = item.get("sink")?.as_str().unwrap_or("Unknown sink"); + Some(format!("{}. {} (flows from {} to {})", i + 1, name, from, to)) + }) + .collect(); + format!("**Interactions in the model:**\n{}", interaction_list.join("\n")) + } + }) + .unwrap_or("No interactions section found in model data.".to_string()) + } else { + "Unable to parse model data.".to_string() + }; + format!("{}\n\n{}", model_info, interactions_data) + } else { + format!("{}\n\n💡 **Note**: This factual analysis is from your loaded model data. For enhanced AI analysis, ensure Ollama is running:\n```\nollama pull llama3.2:3b\nollama run llama3.2:3b\n```", model_info) + } + } +} + +impl Default for MinimalLLMService { + fn default() -> Self { + Self::new().unwrap() + } +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a3890e8..18fd28c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,14 +1,14 @@ use std::{ - ops::Deref, path::PathBuf, - sync::{Arc, Mutex}, + sync::Mutex, }; -use leptos::task::spawn_local; use serde::{Deserialize, Serialize}; -use tauri::{AppHandle, Manager, State}; +use tauri::AppHandle; use tauri_plugin_dialog::DialogExt; -use tauri_plugin_fs::FilePath; + +mod chat_service; +use chat_service::{MinimalLLMService, ChatRequest, ChatResponse}; #[derive(Serialize, Deserialize)] struct Data { @@ -58,14 +58,69 @@ async fn pick_file(app_handle: AppHandle) -> Option { } #[tauri::command] -fn load_file(app_handle: AppHandle, pb: PathBuf) -> Result { +fn load_file(_app_handle: AppHandle, pb: PathBuf) -> Result { let data = std::fs::read(&pb).map_err(|err| format!("Error reading file: {:?}", err))?; + + // Try to parse and store the model data for chat + if let Ok(json_str) = String::from_utf8(data.clone()) { + // Validate it's valid JSON and store it for chat + if serde_json::from_str::(&json_str).is_ok() { + update_current_model(json_str); + } + } + Ok(FileData { data, path: pb.to_str().unwrap().to_string(), }) } +// Global chat service instance +static CHAT_SERVICE: Mutex> = Mutex::new(None); + +// Global current model data +static CURRENT_MODEL_DATA: Mutex> = Mutex::new(None); + +#[tauri::command] +fn update_current_model(model_data: String) { + let mut data = CURRENT_MODEL_DATA.lock().unwrap(); + *data = Some(model_data); +} + +#[tauri::command] +fn get_current_model() -> Option { + let data = CURRENT_MODEL_DATA.lock().unwrap(); + data.clone() +} + +#[tauri::command] +async fn chat_with_model(message: String, model_data: String) -> Result { + // Initialize chat service if not already done + { + let mut service = CHAT_SERVICE.lock().unwrap(); + if service.is_none() { + *service = Some(MinimalLLMService::new().map_err(|e| e.to_string())?); + } + } + + // Try to get the current model data, fallback to the provided model_data + let current_model = get_current_model().unwrap_or(model_data); + + // Create request outside the lock + let request = ChatRequest { + message, + model_context: current_model, + }; + + // Get a clone of the service to avoid holding the lock across await + let service = { + let guard = CHAT_SERVICE.lock().unwrap(); + guard.as_ref().unwrap().clone() + }; + + service.chat(request).await.map_err(|e| e.to_string()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -76,7 +131,10 @@ pub fn run() { save_to_file, save_with_dialog, pick_file, - load_file + load_file, + update_current_model, + get_current_model, + chat_with_model ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/bevy_app/data_model/load.rs b/src/bevy_app/data_model/load.rs index a99c6f7..625776b 100644 --- a/src/bevy_app/data_model/load.rs +++ b/src/bevy_app/data_model/load.rs @@ -11,7 +11,6 @@ use crate::LoadFileEvent; use bevy::prelude::*; use bevy::utils::HashMap; use rust_decimal_macros::dec; - fn load_from_bytes(bytes: &[u8]) -> WorldModel { serde_json::from_slice(bytes).expect("This shouldn't fail") } diff --git a/src/leptos_app/components/chat.rs b/src/leptos_app/components/chat.rs new file mode 100644 index 0000000..5752387 --- /dev/null +++ b/src/leptos_app/components/chat.rs @@ -0,0 +1,251 @@ +use leptos::*; +use leptos::prelude::*; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::spawn_local; +use js_sys; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"])] + async fn invoke(cmd: &str, args: JsValue) -> JsValue; +} + +// Check if we're in a Tauri environment +fn is_tauri_environment() -> bool { + // Simple check: if window.__TAURI__ exists, we're in Tauri + web_sys::window() + .map(|window| { + js_sys::Reflect::has(&window, &"__TAURI__".into()).unwrap_or(false) + }) + .unwrap_or(false) +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct ChatRequest { + message: String, + #[serde(rename = "modelData")] + model_data: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct ChatResponse { + response: String, + error: Option, +} + +#[derive(Clone, Debug)] +struct ChatMessage { + content: String, + is_user: bool, +} + +#[component] +pub fn ChatPanel() -> impl IntoView { + let (messages, set_messages) = signal(Vec::::new()); + let (input_value, set_input_value) = signal(String::new()); + let (is_loading, set_is_loading) = signal(false); + let (is_open, set_is_open) = signal(false); + + let send_message = move |message: String| { + if message.trim().is_empty() || is_loading.get() { + return; + } + + // Immediately add user message and set loading state + set_messages.update(|msgs| { + msgs.push(ChatMessage { + content: message.clone(), + is_user: true, + }); + }); + + set_is_loading.set(true); + set_input_value.set(String::new()); + + // Spawn the async task + spawn_local(async move { + // Get current model data - try to get from Tauri first, fallback to placeholder + let model_data = if is_tauri_environment() { + // Try to get current model data from Tauri + let result = invoke("get_current_model", serde_wasm_bindgen::to_value(&()).unwrap()).await; + match serde_wasm_bindgen::from_value::>(result) { + Ok(Some(data)) => data, + _ => r#"{"environment":{"info":{"name":"Sample System"},"systems":[],"interactions":[]}}"#.to_string() + } + } else { + // Fallback to placeholder if not in Tauri environment + r#"{"environment":{"info":{"name":"Sample System"},"systems":[],"interactions":[]}}"#.to_string() + }; + + let response_text = if is_tauri_environment() { + // Desktop app: Use Tauri backend + let chat_request = ChatRequest { + message: message.clone(), + model_data, + }; + + match serde_wasm_bindgen::to_value(&chat_request) { + Ok(args) => { + let response_result = invoke("chat_with_model", args).await; + match serde_wasm_bindgen::from_value::(response_result) { + Ok(chat_response) => { + web_sys::console::log_1(&format!("Chat response received: {}", chat_response.response).into()); + chat_response.response + }, + Err(e) => { + web_sys::console::error_1(&format!("Failed to parse chat response: {:?}", e).into()); + "Sorry, there was an error parsing the response.".to_string() + } + } + }, + Err(e) => { + web_sys::console::error_1(&format!("Failed to serialize request: {:?}", e).into()); + "Sorry, there was an error preparing the request.".to_string() + } + } + } else { + // Web browser: Use mock responses + generate_web_mock_response(&message, &model_data) + }; + + // Add bot response and clear loading state + set_messages.update(|msgs| { + msgs.push(ChatMessage { + content: response_text, + is_user: false, + }); + }); + + set_is_loading.set(false); + web_sys::console::log_1(&"Chat response completed".into()); + }); + }; + + let handle_submit = move |ev: leptos::ev::SubmitEvent| { + ev.prevent_default(); + let message = input_value.get(); + if !message.trim().is_empty() && !is_loading.get() { + send_message(message); + } + }; + + let toggle_chat = move |_| { + set_is_open.update(|open| *open = !*open); + }; + + view! { +
+ // Chat toggle button + + + // Chat panel +
+ // Chat header +
+
+

+ {if is_tauri_environment() { "Chat with Model" } else { "Chat Demo (Web)" }} +

+ +
+
+ + // Messages area +
+ +
+ {message.content} +
+
+ } + } + /> + + // Loading indicator + +
+
+
+
+
+
+
+
+
+
+
+ + // Input area +
+
+ + +
+
+
+ + } +} + +// Mock responses for web version +fn generate_web_mock_response(question: &str, _model_context: &str) -> String { + let question_lower = question.to_lowercase(); + + if question_lower.contains("what") && question_lower.contains("system") { + "This is a demo system model for the web version. The chat feature works fully in the desktop app with real model data.".to_string() + } else if question_lower.contains("how many") { + "In the web demo, we show placeholder data. Download the desktop app for full functionality with your actual models.".to_string() + } else if question_lower.contains("overview") || question_lower.contains("summary") { + "This is the web demo of BERT's chat feature. For full model analysis, please use the desktop application which can access your local model files.".to_string() + } else if question_lower.contains("demo") || question_lower.contains("test") { + "Great! The chat interface is working. This is a simple demo for the web version. The desktop app provides full model analysis with your real BERT models.".to_string() + } else { + format!( + "I received your message: '{}'. This is the web demo version. For full chat functionality with your actual system models, please use the desktop app!", + question + ) + } +} \ No newline at end of file diff --git a/src/leptos_app/components/mod.rs b/src/leptos_app/components/mod.rs index f9c4e0b..58728c1 100644 --- a/src/leptos_app/components/mod.rs +++ b/src/leptos_app/components/mod.rs @@ -1,5 +1,6 @@ mod button; mod checkbox; +mod chat; mod divider; mod input_group; mod select_group; @@ -7,6 +8,7 @@ mod slider; mod text_area; pub use button::*; +pub use chat::*; pub use checkbox::*; pub use divider::*; pub use input_group::*; diff --git a/src/leptos_app/mod.rs b/src/leptos_app/mod.rs index 9a779e1..af5ad59 100644 --- a/src/leptos_app/mod.rs +++ b/src/leptos_app/mod.rs @@ -10,12 +10,13 @@ use crate::bevy_app::{ Interface, IsSameAsId, SelectedHighlightHelperAdded, SystemElement, SystemEnvironment, }; use crate::leptos_app::details::Details; +use crate::leptos_app::components::ChatPanel; use crate::{ParentState, Subsystem}; use bevy::prelude::{Name, With}; use leptos::prelude::*; use leptos_bevy_canvas::prelude::{event_l2b, single_query_signal, BevyCanvas}; use leptos_meta::*; -use use_file_dialog::*; +use use_file_dialog::generate_file_loader; pub type InterfaceQuery = (Name, ElementDescription, Interface); pub type InteractionQuery = (Name, ElementDescription, Flow); @@ -121,6 +122,7 @@ pub fn App() -> impl IntoView { is_same_as_id detach_event_sender /> + } }