mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-08 22:08:03 -05:00
Development checkpoint - Web UI enhancements with complete directory structure
This commit is contained in:
124
PATTERN_DESCRIPTIONS/README_Pattern_Descriptions_and_Tags_MGT.md
Normal file
124
PATTERN_DESCRIPTIONS/README_Pattern_Descriptions_and_Tags_MGT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Pattern Descriptions and Tags Management
|
||||
|
||||
This document explains the complete workflow for managing pattern descriptions and tags, including how to process new patterns and maintain metadata.
|
||||
|
||||
## System Overview
|
||||
|
||||
The pattern system follows this hierarchy:
|
||||
1. `~/.config/fabric/patterns/` directory: The source of truth for available patterns
|
||||
2. `pattern_extracts.json`: Contains first 500 words of each pattern for reference
|
||||
3. `pattern_descriptions.json`: Stores pattern metadata (descriptions and tags)
|
||||
4. `web/static/data/pattern_descriptions.json`: Web-accessible copy for the interface
|
||||
|
||||
## Pattern Processing Workflow
|
||||
|
||||
### 1. Adding New Patterns
|
||||
- Add patterns to `~/.config/fabric/patterns/`
|
||||
- Run extract_patterns.py to process new additions:
|
||||
```bash
|
||||
python extract_patterns.py
|
||||
|
||||
The Python Script automatically:
|
||||
- Creates pattern extracts for reference
|
||||
- Adds placeholder entries in descriptions file
|
||||
- Syncs to web interface
|
||||
|
||||
### 2. Pattern Extract Creation
|
||||
The script extracts first 500 words from each pattern's system.md file to:
|
||||
|
||||
- Provide context for writing descriptions
|
||||
- Maintain reference material
|
||||
- Aid in pattern categorization
|
||||
|
||||
### 3. Description and Tag Management
|
||||
Pattern descriptions and tags are managed in pattern_descriptions.json:
|
||||
|
||||
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"patternName": "pattern_name",
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## Completing Pattern Metadata
|
||||
|
||||
### Writing Descriptions
|
||||
1. Check pattern_descriptions.json for "[Description pending]" entries
|
||||
2. Reference pattern_extracts.json for context
|
||||
|
||||
3. How to update Pattern short descriptions (one sentence).
|
||||
|
||||
You can update your descriptions in pattern_descriptions.json manually or using LLM assistance (prefered approach).
|
||||
|
||||
Tell AI to look for "Description pending" entries in this file and write a short description based on the extract info in the pattern_extracts.json file. You can also ask your LLM to add tags for those newly added patterns, using other patterns tag assignments as example.
|
||||
|
||||
### Managing Tags
|
||||
1. Add appropriate tags to new patterns
|
||||
2. Update existing tags as needed
|
||||
3. Tags are stored as arrays: ["TAG1", "TAG2"]
|
||||
4. Edit pattern_descriptions.json directly to modify tags
|
||||
5. Make tags your own. You can delete, replace, amend existing tags.
|
||||
|
||||
## File Synchronization
|
||||
|
||||
The script maintains synchronization between:
|
||||
- Local pattern_descriptions.json
|
||||
- Web interface copy in static/data/
|
||||
- No manual file copying needed
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Run extract_patterns.py when:
|
||||
- Adding new patterns
|
||||
- Updating existing patterns
|
||||
- Modifying pattern structure
|
||||
|
||||
2. Description Writing:
|
||||
- Use pattern extracts for context
|
||||
- Keep descriptions clear and concise
|
||||
- Focus on pattern purpose and usage
|
||||
|
||||
3. Tag Management:
|
||||
- Use consistent tag categories
|
||||
- Apply multiple tags when relevant
|
||||
- Update tags to reflect pattern evolution
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If patterns are not showing in the web interface:
|
||||
1. Verify pattern_descriptions.json format
|
||||
2. Check web static copy exists
|
||||
3. Ensure proper file permissions
|
||||
4. Run extract_patterns.py to resync
|
||||
|
||||
## File Structure
|
||||
|
||||
fabric/
|
||||
├── patterns/ # Pattern source files
|
||||
├── PATTERN_DESCRIPTIONS/
|
||||
│ ├── extract_patterns.py # Pattern processing script
|
||||
│ ├── pattern_extracts.json # Pattern content references
|
||||
│ └── pattern_descriptions.json # Pattern metadata
|
||||
└── web/
|
||||
└── static/
|
||||
└── data/
|
||||
└── pattern_descriptions.json # Web interface copy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
114
PATTERN_DESCRIPTIONS/extract_patterns.py
Normal file
114
PATTERN_DESCRIPTIONS/extract_patterns.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
|
||||
def load_existing_file(filepath):
|
||||
"""Load existing JSON file or return default structure"""
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
return {"patterns": []}
|
||||
|
||||
def get_pattern_extract(pattern_path):
|
||||
"""Extract first 500 words from pattern's system.md file"""
|
||||
system_md_path = os.path.join(pattern_path, "system.md")
|
||||
with open(system_md_path, 'r', encoding='utf-8') as f:
|
||||
content = ' '.join(f.read().split()[:500])
|
||||
return content
|
||||
|
||||
def extract_pattern_info():
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
patterns_dir = os.path.expanduser("~/.config/fabric/patterns")
|
||||
print(f"\nScanning patterns directory: {patterns_dir}")
|
||||
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
existing_extracts = load_existing_file(extracts_path)
|
||||
existing_descriptions = load_existing_file(descriptions_path)
|
||||
|
||||
existing_extract_names = {p["patternName"] for p in existing_extracts["patterns"]}
|
||||
existing_description_names = {p["patternName"] for p in existing_descriptions["patterns"]}
|
||||
print(f"Found existing patterns: {len(existing_extract_names)}")
|
||||
|
||||
new_extracts = []
|
||||
new_descriptions = []
|
||||
|
||||
|
||||
for dirname in sorted(os.listdir(patterns_dir)):
|
||||
# Only log new pattern processing
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Processing new pattern: {dirname}")
|
||||
|
||||
|
||||
pattern_path = os.path.join(patterns_dir, dirname)
|
||||
system_md_path = os.path.join(pattern_path, "system.md")
|
||||
print(f"Checking system.md at: {system_md_path}")
|
||||
|
||||
if os.path.isdir(pattern_path) and os.path.exists(system_md_path):
|
||||
print(f"Valid pattern directory found: {dirname}")
|
||||
try:
|
||||
if dirname not in existing_extract_names:
|
||||
print(f"Creating new extract for: {dirname}")
|
||||
pattern_extract = get_pattern_extract(pattern_path) # Pass directory path
|
||||
new_extracts.append({
|
||||
"patternName": dirname,
|
||||
"pattern_extract": pattern_extract
|
||||
})
|
||||
|
||||
if dirname not in existing_description_names:
|
||||
print(f"Creating new description for: {dirname}")
|
||||
new_descriptions.append({
|
||||
"patternName": dirname,
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing {dirname}: {str(e)}")
|
||||
else:
|
||||
print(f"Invalid pattern directory or missing system.md: {dirname}")
|
||||
|
||||
print(f"\nProcessing summary:")
|
||||
print(f"New extracts created: {len(new_extracts)}")
|
||||
print(f"New descriptions added: {len(new_descriptions)}")
|
||||
|
||||
existing_extracts["patterns"].extend(new_extracts)
|
||||
existing_descriptions["patterns"].extend(new_descriptions)
|
||||
|
||||
return existing_extracts, existing_descriptions, len(new_descriptions)
|
||||
|
||||
|
||||
def update_web_static(descriptions_path):
|
||||
"""Copy pattern descriptions to web static directory"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
static_dir = os.path.join(script_dir, "..", "web", "static", "data")
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
static_path = os.path.join(static_dir, "pattern_descriptions.json")
|
||||
shutil.copy2(descriptions_path, static_path)
|
||||
|
||||
def save_pattern_files():
|
||||
"""Save both pattern files and sync to web"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
extracts_path = os.path.join(script_dir, "pattern_extracts.json")
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
pattern_extracts, pattern_descriptions, new_count = extract_pattern_info()
|
||||
|
||||
# Save files
|
||||
with open(extracts_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(pattern_extracts, f, indent=2, ensure_ascii=False)
|
||||
|
||||
with open(descriptions_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(pattern_descriptions, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Update web static
|
||||
update_web_static(descriptions_path)
|
||||
|
||||
print(f"\nProcessing complete:")
|
||||
print(f"Total patterns: {len(pattern_descriptions['patterns'])}")
|
||||
print(f"New patterns added: {new_count}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
save_pattern_files()
|
||||
|
||||
1721
PATTERN_DESCRIPTIONS/pattern_descriptions.json
Normal file
1721
PATTERN_DESCRIPTIONS/pattern_descriptions.json
Normal file
File diff suppressed because it is too large
Load Diff
828
PATTERN_DESCRIPTIONS/pattern_extracts.json
Normal file
828
PATTERN_DESCRIPTIONS/pattern_extracts.json
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,64 @@
|
||||
# Enhanced Pattern Selection and WEB UI Improvements
|
||||
|
||||
This PR adds several Web UI and functionality improvements to make pattern selection more intuitive and provide better context for each pattern's purpose.
|
||||
|
||||
## Demo
|
||||
Watch the demo video showcasing the new features: https://youtu.be/qVuKhCw_edk
|
||||
|
||||
## Major Improvements
|
||||
|
||||
### Pattern Selection and Description
|
||||
- Added modal interface for pattern selection
|
||||
- Added short pattern descriptions for each pattern
|
||||
- Added Select Pattern to execute from Modal
|
||||
- Added scroll functionality to System Instructions frame
|
||||
- **Added search functionality in pattern selection modal**
|
||||
- Real-time pattern filtering as you type
|
||||
- Case-insensitive partial name matching
|
||||
- Maintains favorites sorting while filtering
|
||||
|
||||
### User Experience
|
||||
- Implemented favorites functionality for quick access to frequently used patterns
|
||||
- Improved YouTube transcript handling
|
||||
- Enhanced UI components for better user experience
|
||||
- **Added Obsidian integration for pattern execution output**
|
||||
- Save pattern results directly to Obsidian from web interface
|
||||
- Configurable note naming
|
||||
- Seamless integration with existing Obsidian workflow
|
||||
|
||||
## Technical Improvements
|
||||
- Added backend support for new features
|
||||
- Improved pattern management and selection
|
||||
- Enhanced state management for patterns and favorites
|
||||
|
||||
## Key Files Modified
|
||||
|
||||
### Backend Changes
|
||||
- `fabric/restapi/`: Added new endpoints and functionality for pattern management
|
||||
- `chat.go`, `patterns.go`: Enhanced pattern handling
|
||||
- `configuration.go`, `models.go`: Added support for new features
|
||||
- **`obsidian.go`: New Obsidian integration endpoints**
|
||||
|
||||
### Frontend Changes
|
||||
- `fabric/web/src/lib/components/`:
|
||||
- `chat/`: Enhanced chat interface components
|
||||
- `patterns/`: New pattern selection components
|
||||
- **Added pattern search functionality**
|
||||
- **Enhanced modal UI with search capabilities**
|
||||
- `ui/modal/`: Modal interface implementation
|
||||
- `fabric/web/src/lib/store/`:
|
||||
- `favorites-store.ts`: New favorites functionality
|
||||
- `pattern-store.ts`: Enhanced pattern management
|
||||
- **`obsidian-store.ts`: New Obsidian integration state management**
|
||||
- `fabric/web/src/lib/services/`:
|
||||
- `transcriptService.ts`: Improved YouTube handling
|
||||
|
||||
### Pattern Descriptions
|
||||
- `fabric/myfiles/`:
|
||||
- `pattern_descriptions.json`: Added detailed pattern descriptions
|
||||
- `extract_patterns.py`: Tool for pattern management
|
||||
|
||||
These improvements make the pattern selection process more intuitive and provide users with better context about each pattern's purpose and functionality. The addition of pattern search and Obsidian integration further enhances the user experience by providing quick access to patterns and seamless integration with external note-taking workflows.
|
||||
|
||||
## Note on Platform Compatibility
|
||||
This implementation was developed and tested on macOS. Some modifications may not be required for Windows users, particularly around system-specific paths and configurations. Windows users may need to adjust certain paths or configurations to match their environment.
|
||||
155
WEB INTERFACE MOD README FILES/language-options.md
Normal file
155
WEB INTERFACE MOD README FILES/language-options.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Language Support Implementation
|
||||
|
||||
## Overview
|
||||
The language support allows switching between languages using qualifiers (--fr, --en) in the chat input. The implementation is simple and effective, working at multiple layers of the application.
|
||||
|
||||
## Components
|
||||
|
||||
### 1. Language Store (language-store.ts)
|
||||
```typescript
|
||||
// Manages language state
|
||||
export const languageStore = writable<string>('');
|
||||
```
|
||||
|
||||
### 2. Chat Input (ChatInput.svelte)
|
||||
- Detects language qualifiers in user input
|
||||
- Updates language store
|
||||
- Strips qualifier from message
|
||||
```typescript
|
||||
// Language qualifier handling
|
||||
if (qualifier === 'fr') {
|
||||
languageStore.set('fr');
|
||||
userInput = userInput.replace(/--fr\s*/, '');
|
||||
} else if (qualifier === 'en') {
|
||||
languageStore.set('en');
|
||||
userInput = userInput.replace(/--en\s*/, '');
|
||||
}
|
||||
|
||||
// After sending message
|
||||
try {
|
||||
await sendMessage(userInput);
|
||||
languageStore.set('en'); // Reset to default after send
|
||||
} catch (error) {
|
||||
console.error('Failed to send message:', error);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Chat Service (ChatService.ts)
|
||||
- Adds language instruction to prompts
|
||||
- Defaults to English if no language specified
|
||||
```typescript
|
||||
const language = get(languageStore) || 'en';
|
||||
const languageInstruction = language !== 'en'
|
||||
? `. Please use the language '${language}' for the output.`
|
||||
: '';
|
||||
const fullInput = userInput + languageInstruction;
|
||||
```
|
||||
|
||||
### 4. Global Settings UI (Chat.svelte)
|
||||
```typescript
|
||||
// Language selector in Global Settings
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Language</Label>
|
||||
<Select bind:value={selectedLanguage}>
|
||||
<option value="">Default</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">French</option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
// Script section
|
||||
let selectedLanguage = $languageStore;
|
||||
$: languageStore.set(selectedLanguage);
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. User Input:
|
||||
- User types message with language qualifier (e.g., "--fr Hello")
|
||||
- ChatInput detects qualifier and updates language store
|
||||
- Qualifier is stripped from message
|
||||
- OR user selects language from Global Settings dropdown
|
||||
|
||||
2. Request Processing:
|
||||
- ChatService gets language from store
|
||||
- Adds language instruction to prompt
|
||||
- Sends to backend
|
||||
|
||||
3. Response:
|
||||
- AI responds in requested language
|
||||
- Response is displayed without modification
|
||||
- Language store is reset to English after message is sent
|
||||
|
||||
## Usage Examples
|
||||
|
||||
1. English (Default):
|
||||
```
|
||||
User: What is the weather?
|
||||
AI: The weather information...
|
||||
```
|
||||
|
||||
2. French:
|
||||
```
|
||||
User: --fr What is the weather?
|
||||
AI: Voici les informations météo...
|
||||
```
|
||||
|
||||
3. Using Global Settings:
|
||||
```
|
||||
1. Select "French" from language dropdown
|
||||
2. Type: What is the weather?
|
||||
3. AI responds in French
|
||||
4. Language resets to English after response
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
1. Simple Design:
|
||||
- No complex language detection
|
||||
- No translation layer
|
||||
- Direct instruction to AI
|
||||
|
||||
2. Stateful:
|
||||
- Language persists until changed
|
||||
- Resets to English on page refresh
|
||||
- Resets to English after each message
|
||||
|
||||
3. Extensible:
|
||||
- Easy to add new languages
|
||||
- Just add new qualifiers and store values
|
||||
- Update Global Settings dropdown options
|
||||
|
||||
4. Error Handling:
|
||||
- Invalid qualifiers are ignored
|
||||
- Unknown languages default to English
|
||||
- Store reset on error to prevent state issues
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always reset language after message:
|
||||
```typescript
|
||||
// Reset stores after successful send
|
||||
languageStore.set('en');
|
||||
```
|
||||
|
||||
2. Default to English:
|
||||
```typescript
|
||||
const language = get(languageStore) || 'en';
|
||||
```
|
||||
|
||||
3. Clear language instruction:
|
||||
```typescript
|
||||
const languageInstruction = language !== 'en'
|
||||
? `. Please use the language '${language}' for the output.`
|
||||
: '';
|
||||
```
|
||||
|
||||
4. Handle UI State:
|
||||
```typescript
|
||||
// In Chat.svelte
|
||||
let selectedLanguage = $languageStore;
|
||||
$: {
|
||||
languageStore.set(selectedLanguage);
|
||||
// Update UI immediately when store changes
|
||||
selectedLanguage = $languageStore;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
# Pattern Search Implementation Plan
|
||||
|
||||
## Component Changes (PatternList.svelte)
|
||||
|
||||
### 1. Add Search Input
|
||||
```svelte
|
||||
<div class="px-4 pb-4 flex gap-4 items-center">
|
||||
<!-- Existing sort options -->
|
||||
<div class="flex-1"> <!-- Add flex-1 to push search to right -->
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input type="radio" bind:group={sortBy} value="alphabetical">
|
||||
Alphabetical
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input type="radio" bind:group={sortBy} value="favorites">
|
||||
Favorites First
|
||||
</label>
|
||||
</div>
|
||||
<!-- New search input -->
|
||||
<div class="w-48"> <!-- Fixed width for search -->
|
||||
<Input
|
||||
type="text"
|
||||
bind:value={searchText}
|
||||
placeholder="Search patterns..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. Add Search Logic
|
||||
```typescript
|
||||
// Add to script section
|
||||
let searchText = ""; // For pattern filtering
|
||||
|
||||
// Modify sortedPatterns to include search
|
||||
$: filteredPatterns = patterns.filter(p =>
|
||||
p.patternName.toLowerCase().includes(searchText.toLowerCase())
|
||||
);
|
||||
|
||||
$: sortedPatterns = sortBy === 'alphabetical'
|
||||
? [...filteredPatterns].sort((a, b) => a.patternName.localeCompare(b.patternName))
|
||||
: [
|
||||
...filteredPatterns.filter(p => $favorites.includes(p.patternName)).sort((a, b) => a.patternName.localeCompare(b.patternName)),
|
||||
...filteredPatterns.filter(p => !$favorites.includes(p.patternName)).sort((a, b) => a.patternName.localeCompare(b.patternName))
|
||||
];
|
||||
```
|
||||
|
||||
### 3. Reset Search on Selection
|
||||
```typescript
|
||||
// In pattern selection click handler
|
||||
searchText = ""; // Reset search before closing modal
|
||||
dispatch('select', pattern.patternName);
|
||||
dispatch('close');
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. Import Input component
|
||||
```typescript
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
```
|
||||
|
||||
2. Add searchText variable and filtering logic
|
||||
3. Update template to include search input
|
||||
4. Add reset logic in pattern selection handler
|
||||
5. Test search functionality:
|
||||
- Partial matches work
|
||||
- Case-insensitive search
|
||||
- Search resets on selection
|
||||
- Layout maintains consistency
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
- Search updates in real-time as user types
|
||||
- Matches are case-insensitive
|
||||
- Matches can be anywhere in pattern name
|
||||
- Search box clears when pattern is selected
|
||||
- Sort options (alphabetical/favorites) still work with filtered results
|
||||
- Maintains existing modal layout and styling
|
||||
269
WEB INTERFACE MOD README FILES/pr-1284-update.md
Normal file
269
WEB INTERFACE MOD README FILES/pr-1284-update.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Enhanced Pattern Selection, Pattern Descriptions, New Pattern TAG System, Language Support and other WEB UI Improvements V3
|
||||
|
||||
This Cummulative PR adds several Web UI and functionality improvements to make pattern selection more intuitive (pattern descriptions), ability to save favorite patterns, powerful multilingual capabilities, a Pattern TAG system, a help reference section, more robust Youtube processing and a variety of ui improvements.
|
||||
|
||||
## 🎥 Demo Video
|
||||
https://youtu.be/05YsSwNV_DA
|
||||
|
||||
|
||||
|
||||
## 🌟 Key Features
|
||||
|
||||
### 1. Web UI and Pattern Selection Improvements
|
||||
- Enhanced pattern selection interface for better user experience
|
||||
- New pattern descriptions section accessible via modal
|
||||
- New pattern favorite list and pattern search functionnality
|
||||
- New Tag system for better pattern organization and filtering
|
||||
- Web UI refinements for clearer interaction
|
||||
- Help section via modal
|
||||
|
||||
### 2. Multilingual Support System
|
||||
- Seamless language switching via UI dropdown
|
||||
- Persistent language state management
|
||||
- Pattern processing now use the selected language seamlessly
|
||||
|
||||
### 3. YouTube Integration Enhancement
|
||||
- Robust language handling for YouTube transcript processing
|
||||
- Chunk-based language maintenance for long transcripts
|
||||
- Consistent language output throughout transcript analysis
|
||||
|
||||
### 4. Enhanced Tag Management Integration
|
||||
|
||||
The tag filtering system has been deeply integrated into the Pattern Selection interface through several UI enhancements:
|
||||
|
||||
1. **Dual-Position Tag Panel**
|
||||
- Sliding panel positioned to the right of pattern modal
|
||||
- Dynamic toggle button that adapts position and text based on panel state
|
||||
- Smooth transitions for opening/closing animations
|
||||
|
||||
2. **Tag Selection Visibility**
|
||||
- New dedicated tag display section in pattern modal
|
||||
- Visual separation through subtle background styling
|
||||
- Immediate feedback showing selected tags with comma separation
|
||||
- Inline reset capability for quick tag clearing
|
||||
|
||||
3. **Improved User Experience**
|
||||
- Clear visual hierarchy between pattern list and tag filtering
|
||||
- Multiple ways to manage tags (panel or quick reset)
|
||||
- Consistent styling with existing design language
|
||||
- Space-efficient tag brick layout in 3-column grid
|
||||
|
||||
4. **Technical Implementation**
|
||||
- Reactive tag state management
|
||||
- Efficient tag filtering logic
|
||||
- Proper event dispatching between components
|
||||
- Maintained accessibility standards
|
||||
- Responsive design considerations
|
||||
|
||||
These enhancements create a more intuitive and efficient pattern discovery experience, allowing users to quickly filter and find relevant patterns while maintaining a clean, modern interface.
|
||||
|
||||
|
||||
## 🛠 Technical Implementation
|
||||
|
||||
### Language Support Architecture
|
||||
```typescript
|
||||
// Language state management
|
||||
export const languageStore = writable<string>('');
|
||||
|
||||
// Chat input language detection
|
||||
if (qualifier === 'fr') {
|
||||
languageStore.set('fr');
|
||||
userInput = userInput.replace(/--fr\s*/, '');
|
||||
}
|
||||
|
||||
// Service layer integration
|
||||
const language = get(languageStore) || 'en';
|
||||
const languageInstruction = language !== 'en'
|
||||
? `. Please use the language '${language}' for the output.`
|
||||
: '';
|
||||
```
|
||||
|
||||
### YouTube Processing Enhancement
|
||||
```typescript
|
||||
// Process stream with language instruction per chunk
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content: string, response?: StreamResponse) => {
|
||||
if (currentLanguage !== 'en') {
|
||||
content = `${content}. Please use the language '${currentLanguage}' for the output.`;
|
||||
}
|
||||
// Update messages...
|
||||
}
|
||||
);
|
||||
```
|
||||
# Pattern Descriptions and Tags Management
|
||||
|
||||
This document explains the complete workflow for managing pattern descriptions and tags, including how to process new patterns and maintain metadata.
|
||||
|
||||
## System Overview
|
||||
|
||||
The pattern system follows this hierarchy:
|
||||
1. `~/.config/fabric/patterns/` directory: The source of truth for available patterns
|
||||
2. `pattern_extracts.json`: Contains first 500 words of each pattern for reference
|
||||
3. `pattern_descriptions.json`: Stores pattern metadata (descriptions and tags)
|
||||
4. `web/static/data/pattern_descriptions.json`: Web-accessible copy for the interface
|
||||
|
||||
## Pattern Processing Workflow
|
||||
|
||||
### 1. Adding New Patterns
|
||||
- Add patterns to `~/.config/fabric/patterns/`
|
||||
- Run extract_patterns.py to process new additions:
|
||||
```bash
|
||||
python extract_patterns.py
|
||||
|
||||
The Python Script automatically:
|
||||
- Creates pattern extracts for reference
|
||||
- Adds placeholder entries in descriptions file
|
||||
- Syncs to web interface
|
||||
|
||||
### 2. Pattern Extract Creation
|
||||
The script extracts first 500 words from each pattern's system.md file to:
|
||||
|
||||
- Provide context for writing descriptions
|
||||
- Maintain reference material
|
||||
- Aid in pattern categorization
|
||||
|
||||
### 3. Description and Tag Management
|
||||
Pattern descriptions and tags are managed in pattern_descriptions.json:
|
||||
|
||||
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"patternName": "pattern_name",
|
||||
"description": "[Description pending]",
|
||||
"tags": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## Completing Pattern Metadata
|
||||
|
||||
### Writing Descriptions
|
||||
1. Check pattern_descriptions.json for "[Description pending]" entries
|
||||
2. Reference pattern_extracts.json for context
|
||||
|
||||
3. How to update Pattern short descriptions (one sentence).
|
||||
|
||||
You can update your descriptions in pattern_descriptions.json manually or using LLM assistance (prefered approach).
|
||||
|
||||
Tell AI to look for "Description pending" entries in this file and write a short description based on the extract info in the pattern_extracts.json file. You can also ask your LLM to add tags for those newly added patterns, using other patterns tag assignments as example.
|
||||
|
||||
### Managing Tags
|
||||
1. Add appropriate tags to new patterns
|
||||
2. Update existing tags as needed
|
||||
3. Tags are stored as arrays: ["TAG1", "TAG2"]
|
||||
4. Edit pattern_descriptions.json directly to modify tags
|
||||
5. Make tags your own. You can delete, replace, amend existing tags.
|
||||
|
||||
## File Synchronization
|
||||
|
||||
The script maintains synchronization between:
|
||||
- Local pattern_descriptions.json
|
||||
- Web interface copy in static/data/
|
||||
- No manual file copying needed
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Run extract_patterns.py when:
|
||||
- Adding new patterns
|
||||
- Updating existing patterns
|
||||
- Modifying pattern structure
|
||||
|
||||
2. Description Writing:
|
||||
- Use pattern extracts for context
|
||||
- Keep descriptions clear and concise
|
||||
- Focus on pattern purpose and usage
|
||||
|
||||
3. Tag Management:
|
||||
- Use consistent tag categories
|
||||
- Apply multiple tags when relevant
|
||||
- Update tags to reflect pattern evolution
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If patterns are not showing in the web interface:
|
||||
1. Verify pattern_descriptions.json format
|
||||
2. Check web static copy exists
|
||||
3. Ensure proper file permissions
|
||||
4. Run extract_patterns.py to resync
|
||||
|
||||
## File Structure
|
||||
|
||||
fabric/
|
||||
├── patterns/ # Pattern source files
|
||||
├── PATTERN_DESCRIPTIONS/
|
||||
│ ├── extract_patterns.py # Pattern processing script
|
||||
│ ├── pattern_extracts.json # Pattern content references
|
||||
│ └── pattern_descriptions.json # Pattern metadata
|
||||
└── web/
|
||||
└── static/
|
||||
└── data/
|
||||
└── pattern_descriptions.json # Web interface copy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 🎯 Usage Examples
|
||||
|
||||
### 1. Using Language Qualifiers
|
||||
```
|
||||
User: What is the weather?
|
||||
AI: The weather information...
|
||||
|
||||
User: --fr What is the weather?
|
||||
AI: Voici les informations météo...
|
||||
```
|
||||
|
||||
### 2. Global Settings
|
||||
1. Select language from dropdown
|
||||
2. All interactions use selected language
|
||||
3. Automatic reset to English after each message
|
||||
|
||||
### 3. YouTube Analysis
|
||||
```
|
||||
User: Analyze this YouTube video --fr
|
||||
AI: [Provides analysis in French, maintaining language throughout the transcript]
|
||||
```
|
||||
|
||||
## 💡 Key Benefits
|
||||
|
||||
1. **Enhanced User Experience**
|
||||
- Intuitive language switching
|
||||
- Consistent language handling
|
||||
- Seamless integration with existing features
|
||||
|
||||
2. **Robust Implementation**
|
||||
- Simple yet powerful design
|
||||
- No complex language detection needed
|
||||
- Direct AI instruction approach
|
||||
|
||||
3. **Maintainable Architecture**
|
||||
- Clean separation of concerns
|
||||
- Stateful language management
|
||||
- Easy to extend for new languages
|
||||
|
||||
4. **YouTube Integration**
|
||||
- Handles long transcripts effectively
|
||||
- Maintains language consistency
|
||||
- Robust chunk processing
|
||||
|
||||
## 🔄 Implementation Notes
|
||||
|
||||
1. **State Management**
|
||||
- Language persists until changed
|
||||
- Resets to English after each message
|
||||
- Handles UI state updates efficiently
|
||||
|
||||
2. **Error Handling**
|
||||
- Invalid qualifiers are ignored
|
||||
- Unknown languages default to English
|
||||
- Proper store reset on errors
|
||||
|
||||
3. **Best Practices**
|
||||
- Clear language instructions
|
||||
- Consistent state management
|
||||
- Robust error handling
|
||||
|
||||
@@ -191,7 +191,7 @@ func writeSSEResponse(w gin.ResponseWriter, response StreamResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectFormat(content string) string {
|
||||
/* func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
strings.HasPrefix(content, "flowchart") ||
|
||||
@@ -208,4 +208,15 @@ func detectFormat(content string) string {
|
||||
return "markdown"
|
||||
}
|
||||
return "plain"
|
||||
} */
|
||||
func detectFormat(content string) string {
|
||||
if strings.HasPrefix(content, "graph TD") ||
|
||||
strings.HasPrefix(content, "gantt") ||
|
||||
strings.HasPrefix(content, "flowchart") ||
|
||||
strings.HasPrefix(content, "sequenceDiagram") ||
|
||||
strings.HasPrefix(content, "classDiagram") ||
|
||||
strings.HasPrefix(content, "stateDiagram") {
|
||||
return "mermaid"
|
||||
}
|
||||
return "markdown"
|
||||
}
|
||||
|
||||
0
web/myfiles/Fabric_obsidian/.gitkeep
Normal file
0
web/myfiles/Fabric_obsidian/.gitkeep
Normal file
@@ -0,0 +1,23 @@
|
||||
```markdown
|
||||
[Language: ]
|
||||
ONE SENTENCE SUMMARY: This video explains how to create and use custom patterns within the Fabric AI project by storing them locally.
|
||||
|
||||
MAIN POINTS:
|
||||
1. Fabric patterns are prompts for solving problems using the Fabric AI project.
|
||||
2. Existing Fabric patterns can be used directly from the command line.
|
||||
3. Custom patterns are stored locally, not in the main Fabric repository.
|
||||
4. The local directory for Fabric patterns is `config/Fabric/patterns`.
|
||||
5. Create a custom directory and copy patterns into the Fabric patterns directory.
|
||||
6. An alias can automate copying custom patterns to the correct directory.
|
||||
7. Custom patterns are used by calling Fabric with the pattern name.
|
||||
8. Local patterns are not shared unless submitted to the Fabric project.
|
||||
9. Custom patterns can be used for personal, business, or journal review tasks.
|
||||
10. AI processes the data, so it is not completely private, even with local patterns.
|
||||
|
||||
TAKEAWAYS:
|
||||
1. Custom patterns extend Fabric's functionality for specific use cases.
|
||||
2. Local storage of patterns allows for private and personalized AI interactions.
|
||||
3. Automating pattern deployment simplifies the creation process.
|
||||
4. Understanding the file structure is key to managing custom patterns.
|
||||
5. Fabric's flexibility supports both shared and private AI pattern usage.
|
||||
```
|
||||
220
web/myfiles/Obsidian_perso_not_share/ChatService copy.ts.md
Normal file
220
web/myfiles/Obsidian_perso_not_share/ChatService copy.ts.md
Normal file
@@ -0,0 +1,220 @@
|
||||
import type {
|
||||
ChatRequest,
|
||||
StreamResponse,
|
||||
ChatError as IChatError,
|
||||
ChatPrompt
|
||||
} from '$lib/interfaces/chat-interface';
|
||||
import { get } from 'svelte/store';
|
||||
import { modelConfig } from '$lib/store/model-store';
|
||||
import { systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { chatConfig } from '$lib/store/chat-config';
|
||||
import { messageStore } from '$lib/store/chat-store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
class LanguageValidator {
|
||||
constructor(private targetLanguage: string) {}
|
||||
|
||||
enforceLanguage(content: string): string {
|
||||
if (this.targetLanguage === 'en') return content;
|
||||
return `[Language: ${this.targetLanguage}]\n${content}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatError extends Error implements IChatError {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string = 'CHAT_ERROR',
|
||||
public readonly details?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ChatError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatService {
|
||||
private validator: LanguageValidator;
|
||||
|
||||
constructor() {
|
||||
this.validator = new LanguageValidator(get(languageStore));
|
||||
}
|
||||
|
||||
private async fetchStream(request: ChatRequest): Promise<ReadableStream<StreamResponse>> {
|
||||
try {
|
||||
console.log('\n=== ChatService Request Start ===');
|
||||
console.log('1. Request details:', {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
promptCount: request.prompts?.length,
|
||||
messageCount: request.messages?.length
|
||||
});
|
||||
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ChatError(`HTTP error! status: ${response.status}`, 'HTTP_ERROR', { status: response.status });
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new ChatError('Response body is null', 'NULL_RESPONSE');
|
||||
}
|
||||
|
||||
return this.createMessageStream(reader);
|
||||
} catch (error) {
|
||||
if (error instanceof ChatError) throw error;
|
||||
throw new ChatError('Failed to fetch chat stream', 'FETCH_ERROR', error);
|
||||
}
|
||||
}
|
||||
|
||||
private cleanPatternOutput(content: string): string {
|
||||
content = content.replace(/^# OUTPUT\s*\n/, '');
|
||||
content = content.replace(/^\s*\n/, '');
|
||||
content = content.replace(/\n\s*$/, '');
|
||||
content = content.replace(/^#\s+([A-Z]+):/gm, '$1:');
|
||||
content = content.replace(/^#\s+([A-Z]+)\s*$/gm, '$1');
|
||||
content = content.trim();
|
||||
content = content.replace(/\n{3,}/g, '\n\n');
|
||||
return content;
|
||||
}
|
||||
|
||||
private createMessageStream(reader: ReadableStreamDefaultReader<Uint8Array>): ReadableStream<StreamResponse> {
|
||||
let buffer = '';
|
||||
const cleanPatternOutput = this.cleanPatternOutput.bind(this);
|
||||
const language = get(languageStore);
|
||||
const validator = new LanguageValidator(language);
|
||||
|
||||
const processResponse = (response: StreamResponse) => {
|
||||
const pattern = get(selectedPatternName);
|
||||
if (pattern) {
|
||||
response.content = cleanPatternOutput(response.content);
|
||||
response.format = 'markdown'; // Set format for pattern responses
|
||||
}
|
||||
if (response.type === 'content') {
|
||||
response.content = validator.enforceLanguage(response.content);
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += new TextDecoder().decode(value);
|
||||
const messages = buffer.split('\n\n').filter(msg => msg.startsWith('data: '));
|
||||
|
||||
if (messages.length > 1) {
|
||||
buffer = messages.pop() || '';
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
let response = JSON.parse(msg.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing stream message:', parseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.startsWith('data: ')) {
|
||||
try {
|
||||
let response = JSON.parse(buffer.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing final message:', parseError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(new ChatError('Stream processing error', 'STREAM_ERROR', error));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
reader.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createChatPrompt(userInput: string, systemPromptText?: string): ChatPrompt {
|
||||
const config = get(modelConfig);
|
||||
const language = get(languageStore);
|
||||
|
||||
const languageInstruction = language !== 'en'
|
||||
? `You MUST respond in ${language} language. All output must be in ${language}. `
|
||||
: '';
|
||||
|
||||
const finalSystemPrompt = languageInstruction + (systemPromptText ?? get(systemPrompt));
|
||||
|
||||
const finalUserInput = language !== 'en'
|
||||
? `${userInput}\n\nIMPORTANT: Respond in ${language} language only.`
|
||||
: userInput;
|
||||
|
||||
return {
|
||||
userInput: finalUserInput,
|
||||
systemPrompt: finalSystemPrompt,
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName)
|
||||
};
|
||||
}
|
||||
|
||||
public async createChatRequest(userInput: string, systemPromptText?: string): Promise<ChatRequest> {
|
||||
const prompt = this.createChatPrompt(userInput, systemPromptText);
|
||||
const config = get(chatConfig);
|
||||
|
||||
return {
|
||||
prompts: [prompt],
|
||||
messages: [],
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
public async streamPattern(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async streamChat(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async processStream(
|
||||
stream: ReadableStream<StreamResponse>,
|
||||
onContent: (content: string, response?: StreamResponse) => void,
|
||||
onError: (error: Error) => void
|
||||
): Promise<void> {
|
||||
const reader = stream.getReader();
|
||||
let accumulatedContent = '';
|
||||
let lastResponse: StreamResponse | undefined;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
if (lastResponse) onContent(accumulatedContent, lastResponse);
|
||||
break;
|
||||
}
|
||||
if (value.type === 'error') {
|
||||
throw new ChatError(value.content, 'STREAM_CONTENT_ERROR');
|
||||
}
|
||||
accumulatedContent += value.content;
|
||||
lastResponse = value;
|
||||
onContent(accumulatedContent, value);
|
||||
}
|
||||
} catch (error) {
|
||||
onError(error instanceof ChatError ? error : new ChatError('Stream processing error', 'STREAM_ERROR', error));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
# Markdown Formatting Issue and Fix
|
||||
|
||||
## Issue Description
|
||||
|
||||
The pattern output in the chat history window stopped displaying in markdown format. This occurred after changes were made to improve pattern descriptions and language support.
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
The issue stemmed from changes in three key areas:
|
||||
|
||||
1. **Pattern Selection Flow**
|
||||
- Added verification steps in pattern selection that interrupted the normal flow
|
||||
- Changed how pattern content was loaded and processed
|
||||
|
||||
2. **Message Format Handling**
|
||||
- Changed how message formats were set and preserved in the chat store
|
||||
- Lost the default markdown formatting for pattern responses
|
||||
|
||||
3. **Format Preservation**
|
||||
- Lost the format preservation logic when updating existing messages
|
||||
- Removed fallback to markdown format for new messages
|
||||
|
||||
## The Fix
|
||||
|
||||
### 1. ChatService.ts
|
||||
|
||||
Restored the original pattern response formatting:
|
||||
```typescript
|
||||
const processResponse = (response: StreamResponse) => {
|
||||
const pattern = get(selectedPatternName);
|
||||
if (pattern) {
|
||||
response.content = cleanPatternOutput(response.content);
|
||||
response.format = 'markdown'; // Set format for pattern responses
|
||||
}
|
||||
if (response.type === 'content') {
|
||||
response.content = validator.enforceLanguage(response.content);
|
||||
}
|
||||
return response;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. chat-store.ts
|
||||
|
||||
Restored format preservation logic:
|
||||
```typescript
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content = content;
|
||||
// Always preserve format from response
|
||||
lastMessage.format = response?.format || lastMessage.format;
|
||||
} else {
|
||||
// Ensure new messages have format from response
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format || 'markdown' // Default to markdown
|
||||
});
|
||||
}
|
||||
|
||||
return newMessages;
|
||||
});
|
||||
```
|
||||
|
||||
## Key Learnings
|
||||
|
||||
1. **Format Chain**
|
||||
- Pattern responses are marked as markdown in ChatService
|
||||
- This format is preserved through the chat store
|
||||
- Messages default to markdown when format is missing
|
||||
|
||||
2. **Minimal Changes**
|
||||
- Fixed markdown formatting without disrupting other features
|
||||
- Kept language support and pattern descriptions working
|
||||
- Only restored specific code related to markdown handling
|
||||
|
||||
3. **Verification**
|
||||
- Pattern output now properly renders in markdown
|
||||
- Language support continues to work
|
||||
- Pattern descriptions display correctly in the modal
|
||||
|
||||
## Future Considerations
|
||||
|
||||
1. **Code Organization**
|
||||
- Keep format handling logic separate from other features
|
||||
- Document format-related code clearly
|
||||
- Consider creating dedicated format handling utilities
|
||||
|
||||
2. **Testing**
|
||||
- Add tests for format preservation
|
||||
- Verify markdown rendering in different scenarios
|
||||
- Test interaction with other features
|
||||
|
||||
3. **Documentation**
|
||||
- Document format handling in the codebase
|
||||
- Note dependencies between components
|
||||
- Explain the format chain from service to display
|
||||
267
web/myfiles/Obsidian_perso_not_share/github-workflow-guide.md
Normal file
267
web/myfiles/Obsidian_perso_not_share/github-workflow-guide.md
Normal file
@@ -0,0 +1,267 @@
|
||||
# FABRIC GitHub Workflow Guide
|
||||
|
||||
## Repository Setup
|
||||
|
||||
### Remote Repositories
|
||||
- `origin` → jmd1010/FABRIC2 (your customized version)
|
||||
- `upstream` → danielmiessler/fabric (original project)
|
||||
- `fabricdm` → jmd1010/fabricDM (for your contributions)
|
||||
|
||||
### Branches
|
||||
- `main` - Your working branch with all customizations
|
||||
- `upstream-updates` - Tracks danielmiessler/fabric main branch
|
||||
|
||||
### Verify Setup
|
||||
```bash
|
||||
# Check remotes
|
||||
git remote -v
|
||||
|
||||
# Should show:
|
||||
origin https://github.com/jmd1010/FABRIC2.git (fetch)
|
||||
origin https://github.com/jmd1010/FABRIC2.git (push)
|
||||
upstream https://github.com/danielmiessler/fabric.git (fetch)
|
||||
upstream https://github.com/danielmiessler/fabric.git (push)
|
||||
fabricdm https://github.com/jmd1010/fabricDM.git (fetch)
|
||||
fabricdm https://github.com/jmd1010/fabricDM.git (push)
|
||||
```
|
||||
|
||||
## Daily Workflows
|
||||
|
||||
### 1. Trivial Changes
|
||||
For small fixes or minor updates:
|
||||
```bash
|
||||
# Work directly on main branch
|
||||
git add <files>
|
||||
git commit -m "Description of changes"
|
||||
git push origin main # Backup to your repo
|
||||
```
|
||||
|
||||
### 2. New Features/Risky Changes
|
||||
For significant changes or experimental features:
|
||||
```bash
|
||||
# Create feature branch
|
||||
git checkout -b feature/new-component main
|
||||
# Work on changes...
|
||||
|
||||
# Regular commits
|
||||
git add <files>
|
||||
git commit -m "Feature progress"
|
||||
|
||||
# Backup branch to origin (optional)
|
||||
git push origin feature/new-component
|
||||
|
||||
# When feature is stable:
|
||||
git checkout main
|
||||
git merge feature/new-component
|
||||
git push origin main
|
||||
|
||||
# Clean up
|
||||
git branch -d feature/new-component
|
||||
git push origin --delete feature/new-component # if you pushed it
|
||||
```
|
||||
|
||||
### 3. Surgical Updates from Upstream
|
||||
|
||||
#### Get Specific File from Upstream
|
||||
```bash
|
||||
# Fetch latest from upstream
|
||||
git fetch upstream
|
||||
|
||||
# Check what changed in specific file
|
||||
git diff upstream/main -- path/to/file
|
||||
|
||||
# Option 1: Cherry-pick specific file
|
||||
git checkout upstream/main -- path/to/file
|
||||
# Then commit if you like the changes
|
||||
|
||||
# Option 2: Checkout file then cherry-pick specific changes
|
||||
git checkout upstream/main -- path/to/file
|
||||
git restore --staged path/to/file # Unstage
|
||||
# Edit file to keep only wanted changes
|
||||
git add path/to/file
|
||||
git commit -m "Cherry-picked specific changes from upstream"
|
||||
```
|
||||
|
||||
#### Resolve Merge Conflicts
|
||||
```bash
|
||||
# If you get conflicts during merge
|
||||
git status # See conflicted files
|
||||
|
||||
# For each conflict, you have options:
|
||||
# 1. Keep your version:
|
||||
git checkout --ours path/to/file
|
||||
git add path/to/file
|
||||
|
||||
# 2. Take their version:
|
||||
git checkout --theirs path/to/file
|
||||
git add path/to/file
|
||||
|
||||
# 3. Manually edit file to combine changes:
|
||||
# Edit file, resolve markers, then:
|
||||
git add path/to/file
|
||||
|
||||
# After resolving all conflicts:
|
||||
git commit -m "Merged with conflict resolutions"
|
||||
```
|
||||
|
||||
## Emergency Recovery
|
||||
|
||||
### Rebuild from Remote
|
||||
If local repository becomes corrupted:
|
||||
|
||||
1. Backup your ENV file:
|
||||
```bash
|
||||
cp /Users/jmdb/.config/fabric/.env ~/env-backup
|
||||
```
|
||||
|
||||
2. Remove corrupted local repo:
|
||||
```bash
|
||||
cd ..
|
||||
mv FABRIC2 FABRIC2-corrupted
|
||||
```
|
||||
|
||||
3. Clone fresh copy:
|
||||
```bash
|
||||
git clone https://github.com/jmd1010/FABRIC2.git
|
||||
cd FABRIC2
|
||||
```
|
||||
|
||||
4. Set up remotes:
|
||||
```bash
|
||||
git remote add upstream https://github.com/danielmiessler/fabric.git
|
||||
git remote add fabricdm https://github.com/jmd1010/fabricDM.git
|
||||
```
|
||||
|
||||
5. Create tracking branch:
|
||||
```bash
|
||||
git branch upstream-updates upstream/main
|
||||
```
|
||||
|
||||
6. Restore ENV file:
|
||||
```bash
|
||||
mkdir -p /Users/jmdb/.config/fabric/
|
||||
cp ~/env-backup /Users/jmdb/.config/fabric/.env
|
||||
```
|
||||
|
||||
### Quick Recovery Steps
|
||||
If you need to undo recent changes:
|
||||
|
||||
```bash
|
||||
# Undo last commit but keep changes staged
|
||||
git reset --soft HEAD^
|
||||
|
||||
# Undo last commit and remove changes
|
||||
git reset --hard HEAD^
|
||||
|
||||
# Revert to specific commit
|
||||
git reset --hard <commit-hash>
|
||||
|
||||
# Reset to remote state
|
||||
git fetch origin
|
||||
git reset --hard origin/main
|
||||
```
|
||||
|
||||
## Contributing Back to danielmiessler/fabric
|
||||
|
||||
1. Create feature branch from upstream:
|
||||
```bash
|
||||
git fetch upstream
|
||||
git checkout -b feature-name upstream/main
|
||||
```
|
||||
|
||||
2. Make changes and commit:
|
||||
```bash
|
||||
git add <files>
|
||||
git commit -m "Descriptive message"
|
||||
```
|
||||
|
||||
3. Push to your fork:
|
||||
```bash
|
||||
git push fabricdm feature-name
|
||||
```
|
||||
|
||||
4. Create PR:
|
||||
- Go to https://github.com/danielmiessler/fabric
|
||||
- Click "New Pull Request"
|
||||
- Choose "compare across forks"
|
||||
- Select your fork and branch
|
||||
- Fill in PR description
|
||||
- Submit
|
||||
|
||||
## Updating Existing PRs
|
||||
|
||||
### Sync fabricDM with Latest Changes
|
||||
When you have new changes in FABRIC2 that you want to add to an existing PR:
|
||||
|
||||
1. Backup fabricDM's .gitignore:
|
||||
```bash
|
||||
# In fabricDM repo
|
||||
cp fabric/.gitignore fabric/.gitignore.backup
|
||||
```
|
||||
|
||||
2. Update fabricDM with your changes (one of two methods):
|
||||
|
||||
Method A - Complete sync:
|
||||
```bash
|
||||
git checkout feature/improvements # or whatever your PR branch is
|
||||
git fetch origin # get latest from FABRIC2
|
||||
git reset --hard origin/main # sync with your latest FABRIC2 state
|
||||
cp fabric/.gitignore.backup fabric/.gitignore # restore fabricDM's .gitignore
|
||||
git add fabric/.gitignore
|
||||
git commit -m "Restore fabricDM gitignore"
|
||||
git push -f fabricdm feature/improvements
|
||||
```
|
||||
|
||||
Method B - Merge approach:
|
||||
```bash
|
||||
git checkout feature/improvements
|
||||
git pull origin main # merge your latest FABRIC2 changes
|
||||
git checkout --ours fabric/.gitignore # keep fabricDM's version
|
||||
git add fabric/.gitignore
|
||||
git commit -m "Merge main while preserving fabricDM gitignore"
|
||||
git push fabricdm feature/improvements
|
||||
```
|
||||
|
||||
### Important Notes:
|
||||
- Always protect fabricDM's .gitignore as it's specifically configured for contributions
|
||||
- The backup step ensures you don't accidentally lose ENV file protection
|
||||
- When in doubt, verify .gitignore content after any sync operation
|
||||
- Consider Method B if you want to preserve PR commit history
|
||||
- Use Method A for a clean slate that matches your FABRIC2 state exactly
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always work in feature branches for significant changes
|
||||
2. Regularly push to origin/main to backup your work
|
||||
3. Keep commits atomic and well-described
|
||||
4. Before starting new work:
|
||||
```bash
|
||||
git status # Ensure clean working directory
|
||||
git pull origin main # Get latest changes
|
||||
```
|
||||
5. When in doubt, create a branch:
|
||||
```bash
|
||||
git checkout -b backup/before-risky-change
|
||||
```
|
||||
|
||||
## Additional Tips
|
||||
|
||||
1. Before major changes:
|
||||
- Create a backup branch
|
||||
- Push to origin
|
||||
- Document what you're about to do
|
||||
|
||||
2. When updating from upstream:
|
||||
- Always work in a branch first
|
||||
- Test thoroughly before merging to main
|
||||
- Keep notes of any manual conflict resolutions
|
||||
|
||||
3. Maintaining clean history:
|
||||
- Use meaningful commit messages
|
||||
- Group related changes in single commits
|
||||
- Push to origin main regularly
|
||||
|
||||
4. Emergency Situations:
|
||||
- Don't panic - your code is safe in origin/main
|
||||
- Create backup branches liberally
|
||||
- When in doubt, clone fresh and rebuild
|
||||
134
web/myfiles/Obsidian_perso_not_share/githut note fabric reset.md
Normal file
134
web/myfiles/Obsidian_perso_not_share/githut note fabric reset.md
Normal file
@@ -0,0 +1,134 @@
|
||||
## Repository Structure
|
||||
- Fork: jmd1010/fabric-contrib
|
||||
- Original: danielmiessler/fabric
|
||||
- Branch: feature/web-ui-enhancements
|
||||
|
||||
## Remote Configuration
|
||||
- origin: git@github.com:jmd1010/fabricDM.git
|
||||
- upstream: https://github.com/danielmiessler/fabric.git
|
||||
|
||||
## Development Environment
|
||||
### Shell Configurations
|
||||
- Updated .zshrc and .bashrc
|
||||
- Corrected paths for new repository structure
|
||||
- Go environment variables
|
||||
- Pattern directory mappings
|
||||
|
||||
### Web Development Setup
|
||||
- Backend: fabric --serve
|
||||
- Frontend: npm run dev in web directory
|
||||
- Development URLs and ports
|
||||
|
||||
## Pull Request Workflow
|
||||
1. Sync with upstream
|
||||
2. Feature branch development
|
||||
3. Code review preparation
|
||||
4. PR submission guidelines
|
||||
|
||||
## Backup Management
|
||||
### Repository Setup
|
||||
- Backup: git@github.com:jmd1010/fabric-backup.git
|
||||
- Contains complete development environment
|
||||
- Excludes only sensitive files (.env)
|
||||
- Private repository for safety
|
||||
|
||||
### Backup Workflow
|
||||
1. Regular backup pushes:
|
||||
```bash
|
||||
git push backup feature/web-ui-enhancements
|
||||
|
||||
2. After significant changes:
|
||||
|
||||
git add .
|
||||
git commit -m "Development checkpoint - [description]"
|
||||
git push backup feature/web-ui-enhancements
|
||||
|
||||
3. Before major refactoring:
|
||||
|
||||
git tag backup-pre-refactor-[date]
|
||||
git push backup --tags
|
||||
|
||||
|
||||
Development vs Backup
|
||||
|
||||
Backup repo: Complete environment preservation
|
||||
|
||||
Feature branch: Clean commits for PR
|
||||
|
||||
Separate commit messages for backup vs PR
|
||||
|
||||
Tag significant development points
|
||||
|
||||
Regular synchronization with both repos
|
||||
|
||||
Recovery Procedures
|
||||
|
||||
1. From backup:
|
||||
|
||||
git clone git@github.com:jmd1010/fabric-backup.git
|
||||
git checkout feature/web-ui-enhancements
|
||||
|
||||
2. Environment restoration:
|
||||
|
||||
Copy .env.example to .env
|
||||
Configure local paths
|
||||
Install dependencies
|
||||
Verify development setup
|
||||
|
||||
|
||||
This structure provides:
|
||||
|
||||
1. Clear separation of concerns
|
||||
2. Detailed workflow procedures
|
||||
3. Recovery instructions
|
||||
4. Best practices for both backup and development
|
||||
5. Easy reference for ongoing work
|
||||
|
||||
|
||||
Current Position - Remote Configuration
|
||||
You're at the remote configuration step, which sets up the foundation for both development and backup workflows. Here's how it connects to the broader strategy:
|
||||
Development Track
|
||||
The remotes we're configuring:
|
||||
git remote add upstream git@github.com:danielmiessler/fabric.git
|
||||
|
||||
Copy
|
||||
|
||||
Execute
|
||||
|
||||
terminal
|
||||
git remote add origin git@github.com:jmd1010/fabricDM.git
|
||||
|
||||
Copy
|
||||
|
||||
Execute
|
||||
|
||||
terminal
|
||||
These commands establish:
|
||||
1. Direct link to upstream for pulling latest changes
|
||||
2. Connection to your fork for PR preparation
|
||||
Backup Track
|
||||
Your backup workflow documentation adds an additional remote:
|
||||
git remote add backup git@github.com:jmd1010/fabric-backup.git
|
||||
|
||||
Copy
|
||||
|
||||
Execute
|
||||
|
||||
terminal
|
||||
This creates a three-remote structure:
|
||||
* upstream - Source of truth (danielmiessler/fabric)
|
||||
* origin - Your public fork for PRs
|
||||
* backup - Your private backup repository
|
||||
Integration with Pull Request Workflow
|
||||
The remote setup enables:
|
||||
1. Clean feature branch development (git checkout -b feature/web-ui-enhancements)
|
||||
2. Regular syncs with upstream (git fetch upstream)
|
||||
3. Isolated commits for PR (origin)
|
||||
4. Full environment preservation (backup)
|
||||
Backup Strategy Integration
|
||||
Your backup workflow documentation enhances this by:
|
||||
1. Providing checkpoints via tags
|
||||
2. Maintaining complete development history
|
||||
3. Preserving the full environment state
|
||||
4. Enabling safe experimentation
|
||||
The steps are consistent and complementary, creating a robust development environment with multiple safety nets while maintaining clean PR preparation paths.
|
||||
111
web/myfiles/Obsidian_perso_not_share/language-config-fix.md
Normal file
111
web/myfiles/Obsidian_perso_not_share/language-config-fix.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Language Configuration Fix for YouTube vs Text Processing
|
||||
|
||||
## Working Configuration
|
||||
|
||||
### ChatService.ts
|
||||
- Simple language handling that works:
|
||||
```typescript
|
||||
// Add language instruction at end of input
|
||||
userInput: userInput + languageInstruction
|
||||
```
|
||||
|
||||
### ChatInput.svelte
|
||||
- Store and restore language state:
|
||||
```typescript
|
||||
// Store original language
|
||||
const originalLanguage = get(languageStore);
|
||||
|
||||
// Restore before processing
|
||||
languageStore.set(originalLanguage);
|
||||
|
||||
// Pass isYouTubeTranscript flag
|
||||
await sendMessage(transcript, $systemPrompt, false, true);
|
||||
```
|
||||
|
||||
### chat/+server.ts
|
||||
- Pass language through transcript service:
|
||||
```typescript
|
||||
const response = {
|
||||
transcript,
|
||||
title: videoId,
|
||||
language: body.language
|
||||
};
|
||||
```
|
||||
|
||||
## Issue to Fix: Transcript Display
|
||||
|
||||
The transcript is showing in browser before pattern output because it's being added to the message store. The fix needs to:
|
||||
|
||||
1. Keep the "Processing YouTube transcript..." message
|
||||
2. Skip adding the raw transcript to message store
|
||||
3. Only show the pattern output when it comes back
|
||||
|
||||
### Location of Fix
|
||||
In chat-store.ts:
|
||||
```typescript
|
||||
// Add isYouTubeTranscript parameter to skip adding transcript
|
||||
export async function sendMessage(content: string, systemPromptText?: string, isSystem: boolean = false, isYouTubeTranscript: boolean = false)
|
||||
|
||||
// Skip only the transcript, not system messages
|
||||
if (isYouTubeTranscript && !isSystem) {
|
||||
// Skip only transcript
|
||||
return;
|
||||
}
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: isSystem ? 'system' : 'user',
|
||||
content
|
||||
}]);
|
||||
```
|
||||
|
||||
## Testing
|
||||
1. Regular text with --fr:
|
||||
- Should show message in chat
|
||||
- Should process in French
|
||||
|
||||
2. YouTube with --fr:
|
||||
- Should show "Processing..." message (isSystem=true)
|
||||
- Should NOT show transcript (isYouTubeTranscript=true)
|
||||
- Should show pattern output in French (language preserved)
|
||||
The language-config-fix.md contains valuable insights directly relevant to our current investigation.
|
||||
|
||||
Key findings from language-config-fix.md:
|
||||
|
||||
Previous fix involved handling language state across YouTube transcript processing:
|
||||
// Store original language
|
||||
const originalLanguage = get(languageStore);
|
||||
|
||||
// Restore before processing
|
||||
languageStore.set(originalLanguage);
|
||||
|
||||
Copy
|
||||
|
||||
Apply
|
||||
|
||||
YouTube transcript handling had special flags:
|
||||
await sendMessage(transcript, $systemPrompt, false, true);
|
||||
// The last parameter isYouTubeTranscript was added
|
||||
|
||||
Copy
|
||||
|
||||
Apply
|
||||
|
||||
Chat store modifications:
|
||||
// Skip only the transcript, not system messages
|
||||
if (isYouTubeTranscript && !isSystem) {
|
||||
return;
|
||||
}
|
||||
|
||||
Copy
|
||||
|
||||
Apply
|
||||
|
||||
This reveals:
|
||||
|
||||
YouTube transcripts have special processing requirements
|
||||
Language state needs preservation during transcript handling
|
||||
The chat store treats YouTube transcripts differently
|
||||
Next steps to explore:
|
||||
|
||||
Check if our current implementation maintains these language state safeguards
|
||||
Verify if YouTube transcript flags are properly handled
|
||||
Examine how these changes might interact with markdown formatting
|
||||
@@ -0,0 +1,40 @@
|
||||
# Language Flow Analysis
|
||||
|
||||
## News Article Flow (Working)
|
||||
1. Input: "Here's a news article --fr"
|
||||
2. Language Detection:
|
||||
- Detects --fr
|
||||
- Sets languageStore to 'fr'
|
||||
- Removes --fr flag
|
||||
3. Pattern Execution:
|
||||
- Article text goes directly to ChatService
|
||||
- ChatService adds language instruction
|
||||
- Pattern executes with language flag
|
||||
4. Reset:
|
||||
- Language resets to 'en' after response
|
||||
|
||||
## YouTube Flow (Current)
|
||||
1. Input: "https://youtube.com/... --fr"
|
||||
2. Language Detection:
|
||||
- Detects --fr
|
||||
- Sets languageStore to 'fr'
|
||||
- Removes --fr flag
|
||||
3. Transcript:
|
||||
- Gets raw transcript first
|
||||
- Then sends to ChatService
|
||||
- ChatService adds language instruction
|
||||
- Pattern executes with language flag
|
||||
4. Reset:
|
||||
- Language resets to 'en' after response
|
||||
|
||||
## Key Insight
|
||||
The transcript should be treated exactly like a news article - it's just text that needs to be processed by the pattern with a language flag. The fact that it comes from YouTube doesn't change how the language flag should be handled.
|
||||
|
||||
## Solution Direction
|
||||
1. Keep the language flag handling identical for both cases
|
||||
2. Let the pattern execution handle the language for both:
|
||||
- News articles: text + language flag
|
||||
- YouTube: transcript + language flag
|
||||
3. Reset language only after pattern execution completes
|
||||
|
||||
This way, whether the text comes from direct input or a YouTube transcript, the pattern execution sees the same thing: text content plus a language flag.
|
||||
@@ -0,0 +1,146 @@
|
||||
# Markdown Formatting and Language Header Investigation feb 19
|
||||
|
||||
## Problem Statement
|
||||
1. Large pattern outputs from YouTube transcripts don't render as markdown
|
||||
2. Pattern section headers remain in English when output language is changed
|
||||
|
||||
## Symptoms
|
||||
### Markdown Rendering
|
||||
- Short pattern outputs (e.g., Summary): Render correctly in markdown
|
||||
- Long pattern outputs (e.g., Extract Wisdom): Don't render markdown formatting
|
||||
- Both receive 'complete' message with plain format but only affects large outputs
|
||||
|
||||
### Language Headers
|
||||
- Pattern content translates to requested language
|
||||
- Section headers (SUMMARY, IDEAS, etc.) remain in English
|
||||
- Affects all pattern outputs regardless of size
|
||||
|
||||
## Key Insights
|
||||
1. YouTube transcript endpoint (/api/youtube/transcript/+server.ts) confirmed as irrelevant to markdown issues - it only provides raw text input
|
||||
2. Special YouTube processing flags discovered in language-config-fix.md are significant:
|
||||
- isYouTubeTranscript flag
|
||||
- Language state preservation
|
||||
- Special message store handling
|
||||
|
||||
## Current Understanding
|
||||
1. Markdown issue occurs after pattern processing, not during transcript fetching
|
||||
2. Two distinct flows:
|
||||
- Regular pattern input -> direct processing -> markdown preserved
|
||||
- YouTube input -> transcript flags -> pattern processing -> markdown lost for large outputs
|
||||
|
||||
## Next Investigation Steps
|
||||
1. Trace how isYouTubeTranscript flag affects:
|
||||
- Pattern output processing
|
||||
- Message store handling
|
||||
- Markdown preservation
|
||||
2. Examine language state preservation impact on markdown rendering
|
||||
3. Review complete pipeline from YouTube input to final rendering
|
||||
|
||||
## Files to Focus On
|
||||
1. ChatService.ts - Pattern processing and format determination
|
||||
2. chat-store.ts - Message handling with YouTube flags
|
||||
3. Previous fixes from language-config-fix.md
|
||||
|
||||
|
||||
|
||||
## Investigation Findings
|
||||
### ChatService.ts Analysis
|
||||
1. Format Detection
|
||||
```typescript
|
||||
processResponse() {
|
||||
// Format set to markdown but not consistently maintained
|
||||
// Logs show format changes between content and complete messages
|
||||
}
|
||||
|
||||
2. Language Processing
|
||||
|
||||
createChatPrompt() {
|
||||
// Language instruction doesn't explicitly mention header translation
|
||||
// Mixed language content might affect markdown parsing
|
||||
}
|
||||
|
||||
|
||||
Code Areas Explored
|
||||
ChatService.ts
|
||||
processStream: Content accumulation vs streaming
|
||||
createMessageStream: Chunk processing
|
||||
processResponse: Format determination
|
||||
createChatPrompt: Language instructions
|
||||
Remaining Areas to Investigate
|
||||
ChatService.ts
|
||||
|
||||
LanguageValidator class
|
||||
streamPattern vs streamChat methods
|
||||
Pattern output cleaning
|
||||
Other Files
|
||||
|
||||
chat-interface.ts: Type definitions
|
||||
chat-store.ts: Message handling
|
||||
Markdown rendering component
|
||||
Next Steps
|
||||
Complete ChatService.ts investigation
|
||||
Test LanguageValidator impact
|
||||
Compare streamPattern vs streamChat behavior
|
||||
Examine message store handling
|
||||
Review markdown rendering implementation
|
||||
|
||||
|
||||
## Code Changes Attempted
|
||||
|
||||
### ChatService.ts
|
||||
1. Format Determination
|
||||
```typescript
|
||||
// Attempted to simplify format logic to just markdown/mermaid
|
||||
if (pattern) {
|
||||
response.format = response.content.startsWith('graph TD') ? 'mermaid' : 'markdown';
|
||||
}
|
||||
|
||||
|
||||
2. Stream Processing
|
||||
|
||||
// Tried to eliminate content accumulation since server sends complete content
|
||||
if (value.type === 'content') {
|
||||
onContent(value.content, value);
|
||||
}
|
||||
|
||||
3. Language Instructions
|
||||
// Enhanced language instruction for headers
|
||||
const languageInstruction = language !== 'en'
|
||||
? `You MUST respond in ${language} language. All output including section headers...`
|
||||
: '';
|
||||
|
||||
Results
|
||||
Format changes didn't resolve large output markdown issue
|
||||
Language instruction modification didn't affect header translation
|
||||
Logs revealed consistent pattern in how format changes between content and complete messages
|
||||
|
||||
|
||||
FINAL FIX FOR YOUTUBE MARKDOWN
|
||||
|
||||
Pattern Output Markdown Rendering Fix - Feb 19
|
||||
Problem Identified
|
||||
Large pattern outputs weren't rendering as markdown while small ones did. Investigation revealed different content structures based on output size.
|
||||
|
||||
Root Cause
|
||||
LLM adds ```markdown fences around large outputs
|
||||
Small outputs use direct markdown headers
|
||||
Marked library processed these structures differently
|
||||
Content size wasn't the issue - content structure was
|
||||
Solution Implementation
|
||||
Added fence stripping in ChatService.cleanPatternOutput:
|
||||
|
||||
content = content.replace(/^```markdown\n/, '');
|
||||
content = content.replace(/\n```$/, '');
|
||||
|
||||
Why It Worked
|
||||
Normalized all pattern outputs to consistent markdown structure
|
||||
Removed LLM's explicit formatting markers
|
||||
Allowed marked library to process all outputs uniformly
|
||||
Maintained pattern output cleaning while adding fence handling
|
||||
Technical Flow
|
||||
LLM generates response (with/without fences)
|
||||
ChatService strips fences and cleans output
|
||||
Message store receives clean markdown
|
||||
ChatMessages renders consistent markdown
|
||||
Key Insight
|
||||
The solution focused on content structure normalization rather than size-based handling, resulting in consistent markdown rendering across all pattern outputs.
|
||||
121
web/myfiles/Obsidian_perso_not_share/migrate_tags.py
Normal file
121
web/myfiles/Obsidian_perso_not_share/migrate_tags.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import os
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
|
||||
def load_pattern_tags() -> Dict[str, Any]:
|
||||
"""
|
||||
Load tags and descriptions from pattern_tags_review.md
|
||||
Returns a dictionary with pattern names as keys for exact matching
|
||||
"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
tags_path = os.path.join(script_dir, "pattern_tags_review.md")
|
||||
tags_dict = {}
|
||||
|
||||
with open(tags_path, 'r', encoding='utf-8') as f:
|
||||
current_pattern = None
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("## Pattern:"):
|
||||
current_pattern = line.split("Pattern:")[1].strip()
|
||||
tags_dict[current_pattern] = {"description": "", "tags": []}
|
||||
elif line.startswith("Description:") and current_pattern:
|
||||
tags_dict[current_pattern]["description"] = line.split("Description:")[1].strip()
|
||||
elif line.startswith("Tags:") and current_pattern:
|
||||
# Clean and normalize tag parsing
|
||||
tags_part = line.split("Tags:")[1].strip()
|
||||
tags = [tag.strip() for tag in tags_part.split(",")]
|
||||
tags = [tag for tag in tags if tag] # Remove empty tags
|
||||
tags_dict[current_pattern]["tags"] = tags
|
||||
|
||||
return tags_dict
|
||||
|
||||
def migrate_tags(dry_run: bool = False) -> None:
|
||||
"""
|
||||
Migrate tags to pattern_descriptions.json with detailed verification
|
||||
dry_run: If True, only prints changes without writing files
|
||||
"""
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
descriptions_path = os.path.join(script_dir, "pattern_descriptions.json")
|
||||
|
||||
# Load existing descriptions
|
||||
with open(descriptions_path, 'r', encoding='utf-8') as f:
|
||||
descriptions = json.load(f)
|
||||
|
||||
# Load and verify tags
|
||||
pattern_tags = load_pattern_tags()
|
||||
|
||||
# Track changes for reporting
|
||||
patterns_updated = []
|
||||
patterns_not_found = []
|
||||
patterns_no_tags = []
|
||||
|
||||
# Update descriptions with tags
|
||||
for pattern in descriptions["patterns"]:
|
||||
pattern_name = pattern["patternName"]
|
||||
if pattern_name in pattern_tags:
|
||||
new_tags = pattern_tags[pattern_name]["tags"]
|
||||
if new_tags:
|
||||
old_tags = pattern.get("tags", [])
|
||||
pattern["tags"] = new_tags
|
||||
patterns_updated.append({
|
||||
"name": pattern_name,
|
||||
"old_tags": old_tags,
|
||||
"new_tags": new_tags
|
||||
})
|
||||
else:
|
||||
patterns_no_tags.append(pattern_name)
|
||||
else:
|
||||
patterns_not_found.append(pattern_name)
|
||||
|
||||
# Print detailed migration report
|
||||
print("\nMigration Report:")
|
||||
print("-----------------")
|
||||
print(f"Total patterns in descriptions: {len(descriptions['patterns'])}")
|
||||
print(f"Total patterns with tag data: {len(pattern_tags)}")
|
||||
print(f"Patterns updated: {len(patterns_updated)}")
|
||||
|
||||
print("\nDetailed Updates:")
|
||||
for p in patterns_updated:
|
||||
print(f"\nPattern: {p['name']}")
|
||||
print(f" Old tags: {p['old_tags']}")
|
||||
print(f" New tags: {p['new_tags']}")
|
||||
|
||||
if patterns_not_found:
|
||||
print("\nPatterns without tag data:")
|
||||
for p in patterns_not_found:
|
||||
print(f" - {p}")
|
||||
|
||||
if patterns_no_tags:
|
||||
print("\nPatterns with empty tags:")
|
||||
for p in patterns_no_tags:
|
||||
print(f" - {p}")
|
||||
|
||||
if not dry_run:
|
||||
# Save updated descriptions
|
||||
with open(descriptions_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(descriptions, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Update web static copy
|
||||
web_static_path = os.path.join(script_dir, "..", "web", "static", "data", "pattern_descriptions.json")
|
||||
with open(web_static_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(descriptions, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print("\nFiles updated:")
|
||||
print(f" - {descriptions_path}")
|
||||
print(f" - {web_static_path}")
|
||||
else:
|
||||
print("\nDRY RUN - No files were modified")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# First run in dry-run mode to verify changes
|
||||
print("Performing dry run to verify changes...")
|
||||
migrate_tags(dry_run=True)
|
||||
|
||||
# Prompt for actual migration
|
||||
response = input("\nProceed with actual migration? (yes/no): ")
|
||||
if response.lower() == 'yes':
|
||||
migrate_tags(dry_run=False)
|
||||
print("\nMigration completed successfully")
|
||||
else:
|
||||
print("\nMigration cancelled")
|
||||
|
||||
822
web/myfiles/Obsidian_perso_not_share/pattern_tags_review.md
Normal file
822
web/myfiles/Obsidian_perso_not_share/pattern_tags_review.md
Normal file
@@ -0,0 +1,822 @@
|
||||
# Pattern Tags Review
|
||||
|
||||
## Pattern: agility_story
|
||||
Description: Generate user stories and acceptance criteria for agile development tasks following standard agile methodology formats.
|
||||
Tags: DEVELOPMENT
|
||||
|
||||
## Pattern: ai
|
||||
Description: Interpret questions deeply and provide concise, insightful answers in brief markdown bullets focused on core concepts.
|
||||
Tags: AI, ANALYSIS
|
||||
|
||||
## Pattern: analyze_answers
|
||||
Description: Evaluate student responses against expert knowledge, providing detailed assessment and feedback adapted to specified academic levels.
|
||||
Tags: ANALYSIS, LEARNING
|
||||
|
||||
## Pattern: analyze_candidates
|
||||
Description: Compare political candidates' positions, highlighting key differences in policies, backgrounds, and potential implications of their platforms.
|
||||
Tags: ANALYSIS, RESEARCH
|
||||
|
||||
## Pattern: analyze_cfp_submission
|
||||
Description: Evaluate conference presentation submissions for content quality, speaker qualifications, and potential audience engagement and educational value.
|
||||
Tags: ANALYSIS, REVIEW
|
||||
|
||||
## Pattern: analyze_comments
|
||||
Description: Analyze user comments to determine sentiment patterns, extract key praise and criticism points, and summarize overall reception.
|
||||
Tags: ANALYSIS, EXTRACTION
|
||||
|
||||
## Pattern: analyze_email_headers
|
||||
Description: Analyze email authentication headers (SPF, DKIM, DMARC, ARC) to assess email security and provide actionable recommendations.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: analyze_prose_json
|
||||
Description: Evaluate writing quality and provide structured JSON output rating novelty, clarity, and overall messaging effectiveness.
|
||||
Tags: ANALYSIS, WRITING, DEVELOPMENT
|
||||
|
||||
## Pattern: analyze_prose_pinker
|
||||
Description: Analyze writing style using Steven Pinker's principles from 'The Sense of Style' to improve clarity and effectiveness.
|
||||
Tags: ANALYSIS, WRITING
|
||||
|
||||
## Pattern: ask_uncle_duke
|
||||
Description: Provide expert software development guidance focusing on Java, Spring Framework, and frontend technologies with best practices.
|
||||
Tags: DEVELOPMENT, LEARNING
|
||||
|
||||
## Pattern: capture_thinkers_work
|
||||
Description: Extract and summarize key philosophical concepts, background, and impactful ideas from notable thinkers and their work.
|
||||
Tags: EXTRACTION, SUMMARIZATION, RESEARCH, CRITICAL THINKING
|
||||
|
||||
## Pattern: check_agreement
|
||||
Description: Review contracts and agreements to identify important stipulations, potential issues, and recommended changes for negotiation.
|
||||
Tags: ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: clean_text
|
||||
Description: Format and clean text by fixing line breaks, capitalization, and punctuation while preserving original content and meaning.
|
||||
Tags: WRITING, CONVERSION
|
||||
|
||||
## Pattern: coding_master
|
||||
Description: Explain coding concepts and languages clearly for beginners using examples from reputable sources and proper syntax formatting.
|
||||
Tags: DEVELOPMENT, LEARNING
|
||||
|
||||
## Pattern: compare_and_contrast
|
||||
Description: Create structured comparisons of items in table format, highlighting key differences and similarities across relevant topics.
|
||||
Tags: ANALYSIS, WRITING
|
||||
|
||||
## Pattern: convert_to_markdown
|
||||
Description: Convert content to clean markdown format while preserving complete original content, formatting, and structure.
|
||||
Tags: CONVERSION, WRITING
|
||||
|
||||
## Pattern: create_5_sentence_summary
|
||||
Description: Generate increasingly concise summaries of content in five levels, from five words down to one word.
|
||||
Tags: SUMMARIZATION, WRITING
|
||||
|
||||
## Pattern: create_ai_jobs_analysis
|
||||
Description: Analyze AI's impact on job categories, identifying automation risks and providing strategies for career resilience.
|
||||
Tags: ANALYSIS, AI, BUSINESS
|
||||
|
||||
## Pattern: create_aphorisms
|
||||
Description: Compile relevant, attributed aphorisms from historical figures related to specific topics or themes.
|
||||
Tags: EXTRACTION, WRITING
|
||||
|
||||
## Pattern: create_better_frame
|
||||
Description: Develop positive mental frameworks for viewing challenging situations, emphasizing constructive perspectives and opportunities.
|
||||
Tags: ANALYSIS, STRATEGY, SELF
|
||||
|
||||
|
||||
## Pattern: create_coding_project
|
||||
Description: Design structured coding projects with clear architecture, implementation steps, and best practices using modern technologies.
|
||||
Tags: DEVELOPMENT
|
||||
|
||||
## Pattern: create_command
|
||||
Description: Generate precise CLI commands for penetration testing tools based on documentation and specific requirements.
|
||||
Tags: SECURITY, DEVELOPMENT
|
||||
|
||||
## Pattern: create_cyber_summary
|
||||
Description: Summarize cybersecurity incidents, vulnerabilities, and threats into concise, actionable intelligence briefings.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: create_diy
|
||||
Description: Create detailed, step-by-step DIY tutorials with clear instructions, required materials, and expected outcomes.
|
||||
Tags: WRITING, LEARNING, SELF
|
||||
|
||||
## Pattern: create_formal_email
|
||||
Description: Compose professional email correspondence following business etiquette and maintaining appropriate tone and structure.
|
||||
Tags: WRITING, BUSINESS
|
||||
|
||||
## Pattern: create_git_diff_commit
|
||||
Description: Generate clear git commit messages and commands that effectively communicate code changes and updates.
|
||||
Tags: DEVELOPMENT
|
||||
|
||||
## Pattern: create_graph_from_input
|
||||
Description: Transform security program metrics into CSV data format for visualizing progress and improvements over time.
|
||||
Tags: VISUALIZATION, SECURITY, CONVERSION
|
||||
|
||||
## Pattern: create_hormozi_offer
|
||||
Description: Create compelling business offers using Alex Hormozi's '$100M Offers' methodology and principles.
|
||||
Tags: BUSINESS, WRITING
|
||||
|
||||
## Pattern: create_idea_compass
|
||||
Description: Organize thoughts and concepts using a structured framework analyzing definitions, evidence, relationships, and implications.
|
||||
Tags: ANALYSIS, VISUALIZATION, CRITICAL THINKING
|
||||
|
||||
## Pattern: create_investigation_visualization
|
||||
Description: Create detailed Graphviz visualizations of investigation data showing relationships, activities, and key findings.
|
||||
Tags: VISUALIZATION, SECURITY, ANALYSIS
|
||||
|
||||
## Pattern: create_logo
|
||||
Description: Generate minimalist logo design prompts that capture brand essence through simple, elegant vector graphics.
|
||||
Tags: VISUALIZATION, BUSINESS
|
||||
|
||||
## Pattern: create_markmap_visualization
|
||||
Description: Transform complex ideas into hierarchical mind maps using Markmap syntax for visual concept representation.
|
||||
Tags: VISUALIZATION, CONVERSION, CRITICAL THINKING
|
||||
|
||||
## Pattern: create_mermaid_visualization_for_github
|
||||
Description: Create GitHub-compatible Mermaid diagrams to visualize workflows, architectures, and processes in documentation.
|
||||
Tags: VISUALIZATION, DEVELOPMENT
|
||||
|
||||
## Pattern: create_newsletter_entry
|
||||
Description: Write concise, engaging newsletter content in Frontend Weekly style, focusing on key developments and insights.
|
||||
Tags: WRITING, SUMMARIZATION, BUSINESS
|
||||
|
||||
## Pattern: create_npc
|
||||
Description: Generate detailed D&D 5E NPC characters with comprehensive backgrounds, personalities, and game statistics.
|
||||
Tags: GAMING
|
||||
|
||||
## Pattern: create_pattern
|
||||
Description: Design structured patterns for AI prompts with clear identity, purpose, steps, and output instructions.
|
||||
Tags: AI, DEVELOPMENT
|
||||
|
||||
## Pattern: create_prediction_block
|
||||
Description: Format and structure predictions from content for tracking and verification in markdown-based prediction logs.
|
||||
Tags: AI, ANALYSIS, WRITING
|
||||
|
||||
## Pattern: create_recursive_outline
|
||||
Description: Break down complex tasks or ideas into hierarchical, actionable components through recursive decomposition.
|
||||
Tags: ANALYSIS, VISUALIZATION
|
||||
|
||||
## Pattern: create_report_finding
|
||||
Description: Document security assessment findings with clear descriptions, risks, recommendations, and supporting evidence.
|
||||
Tags: SECURITY
|
||||
|
||||
|
||||
## Pattern: create_rpg_summary
|
||||
Description: Summarize RPG gaming sessions capturing key events, combat encounters, and narrative developments in structured format.
|
||||
Tags: GAMING
|
||||
|
||||
## Pattern: create_security_update
|
||||
Description: Compile concise security newsletters covering current threats, advisories, vulnerabilities, and critical cybersecurity developments with relevant links.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: create_show_intro
|
||||
Description: Craft compelling podcast or show introductions that effectively preview content and engage audience interest.
|
||||
Tags: WRITING
|
||||
|
||||
## Pattern: create_story_explanation
|
||||
Description: Transform complex concepts into clear, engaging narratives that effectively communicate ideas and implications.
|
||||
Tags: WRITING, LEARNING
|
||||
|
||||
## Pattern: create_tags
|
||||
Description: Generate relevant, single-word tags for content categorization and mind mapping organization.
|
||||
Tags: ANALYSIS, EXTRACTION, WRITING
|
||||
|
||||
## Pattern: create_threat_scenarios
|
||||
Description: Develop realistic security threat scenarios based on risk analysis and attacker motivation assessment.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: create_ttrc_graph
|
||||
Description: Generate time-series data for visualizing critical vulnerability remediation metrics and progress.
|
||||
Tags: SECURITY, VISUALIZATION
|
||||
|
||||
## Pattern: create_ttrc_narrative
|
||||
Description: Create compelling narratives demonstrating security program improvements in vulnerability remediation efficiency.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: create_upgrade_pack
|
||||
Description: Extract world model updates and task algorithms from content to improve decision-making and processes.
|
||||
Tags: EXTRACTION, ANALYSIS, BUSINESS, CRITICAL THINKING
|
||||
|
||||
## Pattern: create_video_chapters
|
||||
Description: Extract and organize video content into timestamped chapters highlighting key topics and moments.
|
||||
Tags: EXTRACTION, VISUALIZATION
|
||||
|
||||
## Pattern: create_visualization
|
||||
Description: Transform complex concepts into ASCII art visualizations with detailed explanations of represented relationships.
|
||||
Tags: VISUALIZATION
|
||||
|
||||
## Pattern: dialog_with_socrates
|
||||
Description: Engage in Socratic dialogue to explore ideas through questioning and critical examination of assumptions.
|
||||
Tags: ANALYSIS, LEARNING, SELF, CRITICAL THINKING
|
||||
|
||||
## Pattern: analyze_paper
|
||||
Description: Analyze a scientific paper to identify its primary findings and assess the quality and rigor of its conclusions.
|
||||
Tags: ANALYSIS, RESEARCH, LEARNING
|
||||
|
||||
## Pattern: create_summary
|
||||
Description: Generate concise summaries of content by extracting key points, main ideas, and important takeaways into a structured format.
|
||||
Tags: SUMMARIZATION, WRITING
|
||||
|
||||
## Pattern: extract_wisdom
|
||||
Description: Extract insightful ideas, practical habits, and meaningful recommendations from content, focusing on life wisdom and human flourishing.
|
||||
Tags: EXTRACTION, ANALYSIS, SUMMARIZATION, SELF
|
||||
|
||||
## Pattern: create_design_document
|
||||
Description: Create comprehensive software architecture documentation using the C4 model, including business context, security posture, and detailed system design.
|
||||
Tags: DEVELOPMENT, WRITING, VISUALIZATION
|
||||
|
||||
## Pattern: create_stride_threat_model
|
||||
Description: Generate detailed threat models using STRIDE methodology to identify, analyze, and prioritize security threats for system components.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: extract_main_idea
|
||||
Description: Identify and distill the single most important or insightful idea from content, providing both the core concept and actionable recommendation.
|
||||
Tags: ANALYSIS, EXTRACTION, SUMMARIZATION
|
||||
|
||||
## Pattern: create_mermaid_visualization
|
||||
Description: Transform complex concepts and relationships into clear visual diagrams using Mermaid syntax, creating standalone visualizations that effectively convey ideas.
|
||||
Tags: VISUALIZATION, DEVELOPMENT
|
||||
|
||||
## Pattern: create_prd
|
||||
Description: Create precise and structured Product Requirements Documents (PRDs) from input requirements and specifications.
|
||||
Tags: DEVELOPMENT, WRITING, BUSINESS
|
||||
|
||||
## Pattern: explain_code
|
||||
Description: Analyze and explain code, security tool outputs, and configuration files, providing clear explanations of their functionality and implications.
|
||||
Tags: DEVELOPMENT, LEARNING
|
||||
|
||||
## Pattern: create_sigma_rules
|
||||
Description: Extract Tactics, Techniques, and Procedures from security publications and translate them into YAML-based Sigma detection rules.
|
||||
Tags: SECURITY, DEVELOPMENT
|
||||
|
||||
## Pattern: extract_predictions
|
||||
Description: Identify and analyze predictions from content, extracting specific claims, timeframes, confidence levels, and verification criteria.
|
||||
Tags: ANALYSIS, EXTRACTION, CRITICAL THINKING
|
||||
|
||||
## Pattern: create_user_story
|
||||
Description: Write clear, concise technical user stories with descriptions and acceptance criteria for software features and improvements.
|
||||
Tags: DEVELOPMENT, WRITING
|
||||
|
||||
## Pattern: analyze_threat_report
|
||||
Description: Extract and analyze key insights, trends, statistics, and recommendations from cybersecurity threat reports in a structured format.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: analyze_malware
|
||||
Description: Analyze malware behavior, extract indicators of compromise, identify MITRE ATT&CK techniques, and provide detection recommendations across multiple platforms.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: extract_book_recommendations
|
||||
Description: Extract and prioritize practical recommendations and actionable advice from books, presenting them in a clear, instructive format.
|
||||
Tags: EXTRACTION, SUMMARIZATION, SELF
|
||||
|
||||
[...continuing with remaining patterns...]
|
||||
|
||||
## Pattern: create_art_prompt
|
||||
Description: Transform concepts into detailed AI art prompts, incorporating style references and emotional elements to guide visual creation.
|
||||
Tags: AI, VISUALIZATION
|
||||
|
||||
## Pattern: create_network_threat_landscape
|
||||
Description: Analyze network ports and services to create comprehensive threat reports with risks, trends, and security recommendations.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: create_academic_paper
|
||||
Description: Transform content into high-quality academic papers using LaTeX formatting with professional layout and authoritative presentation.
|
||||
Tags: WRITING, RESEARCH, LEARNING
|
||||
|
||||
## Pattern: create_keynote
|
||||
Description: Design TED-style presentations with narrative flow, concise slide content, speaker notes, and image descriptions for maximum impact.
|
||||
Tags: WRITING, VISUALIZATION
|
||||
|
||||
## Pattern: extract_core_message
|
||||
Description: Distill the fundamental message from presentations, essays, or bodies of work into a single, clear, impactful sentence.
|
||||
Tags: ANALYSIS, SUMMARIZATION
|
||||
|
||||
## Pattern: create_reading_plan
|
||||
Description: Design structured, three-phase reading plans with core, extended, and exploratory materials to build comprehensive knowledge of topics.
|
||||
Tags: LEARNING, SELF
|
||||
|
||||
## Pattern: extract_extraordinary_claims
|
||||
Description: Identify and extract claims that contradict scientific consensus, are difficult to verify, or conflict with expert understanding.
|
||||
Tags: ANALYSIS, RESEARCH, CRITICAL THINKING
|
||||
|
||||
## Pattern: create_quiz
|
||||
Description: Generate targeted review questions based on learning objectives, adapting difficulty to specified student levels and subject matter.
|
||||
Tags: LEARNING
|
||||
|
||||
## Pattern: extract_skills
|
||||
Description: Extract and classify hard and soft skills from job descriptions, organizing them into a structured skill inventory.
|
||||
Tags: EXTRACTION, ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: create_micro_summary
|
||||
Description: Generate ultra-concise content summaries with one-sentence overview, key points, and essential takeaways in minimal words.
|
||||
Tags: SUMMARIZATION, WRITING
|
||||
|
||||
## Pattern: extract_insights
|
||||
Description: Extract powerful insights about life, technology, and human flourishing from content, presenting them as concise, actionable bullet points.
|
||||
Tags: EXTRACTION, SELF
|
||||
|
||||
## Pattern: analyze_claims
|
||||
Description: Evaluate truth claims by analyzing supporting evidence, counter-arguments, and logical fallacies to provide balanced, objective assessments.
|
||||
Tags: ANALYSIS, RESEARCH, CRRITICAL THINKING
|
||||
|
||||
## Pattern: analyze_debate
|
||||
Description: Analyze debate transcripts to identify key arguments, agreements, misunderstandings, and insights while measuring emotional intensity and participant interaction.
|
||||
Tags: ANALYSIS, SUMMARIZATION, CRITICAL THINKING
|
||||
|
||||
## Pattern: analyze_incident
|
||||
Description: Extract and organize critical information from cybersecurity breach articles, including attack details, impact analysis, and actionable recommendations.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: analyze_interviewer_techniques
|
||||
Description: Study interviewer questions and methods to identify effective techniques, patterns, and strategies that lead to exceptional interviews.
|
||||
Tags: ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: analyze_military_strategy
|
||||
Description: Examine historical battles by analyzing strategic decisions, tactical approaches, and pivotal moments to extract key military lessons.
|
||||
Tags: ANALYSIS, STRATEGY
|
||||
|
||||
## Pattern: analyze_logs
|
||||
Description: Examine server logs to identify patterns, anomalies, and potential issues, providing insights into system reliability and performance improvements.
|
||||
Tags: DEVELOPMENT, SECURITY
|
||||
|
||||
## Pattern: analyze_mistakes
|
||||
Description: Analyze past thinking errors and mistaken beliefs to identify patterns and prevent similar mistakes in current predictions and decisions.
|
||||
Tags: ANALYSIS, STRATEGY, BUISNESS, SELF, CRITICAL THINKING
|
||||
|
||||
## Pattern: analyze_personality
|
||||
Description: Perform psychological analysis of individuals by examining their language, responses, and behaviors to reveal underlying personality traits and patterns.
|
||||
Tags: ANALYSIS, RESEARCH, SELF
|
||||
|
||||
## Pattern: analyze_presentation
|
||||
Description: Evaluate presentations by scoring content novelty, speaker selflessness, and entertainment value to provide actionable feedback for improvement.
|
||||
Tags: ANALYSIS, REVIEW, BUSINESS
|
||||
|
||||
## Pattern: analyze_product_feedback
|
||||
Description: Process and categorize user feedback to identify key themes, consolidate similar inputs, and prioritize actionable insights for product improvements.
|
||||
Tags: ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: analyze_proposition
|
||||
Description: Examine ballot propositions to assess their purpose, potential impact, supporting arguments, and implications for stakeholders and regulations.
|
||||
Tags: ANALYSIS, RESEARCH
|
||||
|
||||
## Pattern: analyze_prose
|
||||
Description: Evaluate writing quality by rating novelty of ideas, clarity of presentation, and prose style to provide targeted improvement recommendations.
|
||||
Tags: ANALYSIS, WRITING, REVIEW
|
||||
|
||||
## Pattern: analyze_risk
|
||||
Description: Assess third-party vendor security compliance and privacy standards to determine risk levels and recommend appropriate security controls.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: analyze_sales_call
|
||||
Description: Evaluate sales call performance by analyzing pitch alignment, sales fundamentals, and customer interaction to provide actionable improvement recommendations.
|
||||
Tags: ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: analyze_spiritual_text
|
||||
Description: Compare and contrast religious texts with the King James Bible, identifying unique claims, differences in tenets, and doctrinal variations.
|
||||
Tags: ANALYSIS, RESEARCH, SELF
|
||||
|
||||
## Pattern: analyze_tech_impact
|
||||
Description: Evaluate technology projects' societal impact by analyzing outcomes, ethical considerations, and sustainability across environmental, economic, and social dimensions.
|
||||
Tags: ANALYSIS, RESEARCH, BUSINESS
|
||||
|
||||
## Pattern: analyze_threat_report_trends
|
||||
Description: Extract and analyze surprising, insightful, and significant trends from cybersecurity threat reports to identify emerging patterns and developments.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: answer_interview_question
|
||||
Description: Generate natural, experience-based responses to technical interview questions that demonstrate expertise while maintaining conversational tone and conciseness.
|
||||
Tags: DEVELOPMENT, LEARNING
|
||||
|
||||
## Pattern: ask_secure_by_design_questions
|
||||
Description: Generate comprehensive security-focused questions to guide secure system design and implementation across different architectural components.
|
||||
Tags: SECURITY, DEVELOPMENT
|
||||
|
||||
## Pattern: analyze_patent
|
||||
Description: Analyze patent documents to evaluate novelty, inventive steps, and technical advantages while providing detailed explanations of problems solved and implementation approaches.
|
||||
Tags: ANALYSIS, BUISNESS
|
||||
|
||||
## Pattern: analyze_threat_report_cmds
|
||||
Description: Extract and interpret cybersecurity commands from threat reports, providing detailed command-line arguments and implementation guidance from diverse expert perspectives.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: enrich_blog_post
|
||||
Description: Enhance markdown blog posts by improving structure, visuals, and formatting to optimize readability and engagement for static site generation.
|
||||
Tags: WRITING, VISUALIZATION
|
||||
|
||||
## Pattern: explain_docs
|
||||
Description: Transform technical documentation and instructions into clearer, more accessible explanations with improved structure and practical usage examples.
|
||||
Tags: WRITING, DEVELOPMENT
|
||||
|
||||
## Pattern: explain_math
|
||||
Description: Explain mathematical concepts and equations clearly for students using step-by-step instructions, visual aids, and practical examples.
|
||||
Tags: LEARNING
|
||||
|
||||
## Pattern: explain_project
|
||||
Description: Create comprehensive project overviews with installation instructions, usage examples, and practical applications while maintaining clear documentation structure.
|
||||
Tags: DEVELOPMENT, BUSINESS
|
||||
|
||||
## Pattern: explain_terms
|
||||
Description: Create comprehensive glossaries of advanced terms from content, providing clear definitions, explanations, and helpful analogies for better understanding.
|
||||
Tags: WRITING, LEARNING
|
||||
|
||||
## Pattern: export_data_as_csv
|
||||
Description: Extract structured data from content and convert it into properly formatted CSV files while preserving relationships and data integrity.
|
||||
Tags: CONVERSION, DEVELOPMENT
|
||||
|
||||
## Pattern: extract_algorithm_update_recommendations
|
||||
Description: Extract concise, practical recommendations for improving algorithms and processes from content, focusing on actionable implementation steps.
|
||||
Tags: EXTRACTION, DEVELOPMENT, ANALYSIS, WISDOM
|
||||
|
||||
## Pattern: extract_article_wisdom
|
||||
Description: Extract key insights, practical advice, and timeless wisdom from articles, organizing them into clear, actionable takeaways.
|
||||
Tags: EXTRACTION, SELF, WISDOM
|
||||
|
||||
## Pattern: extract_book_ideas
|
||||
Description: Identify and extract novel concepts, unique perspectives, and innovative ideas from books that could inspire new projects or creative works.
|
||||
Tags: EXTRACTION, SELF, WISDOM
|
||||
|
||||
## Pattern: extract_business_ideas
|
||||
Description: Identify potential business opportunities, market gaps, and entrepreneurial insights from content, presenting them as actionable venture concepts.
|
||||
Tags: BUSINESS
|
||||
|
||||
## Pattern: extract_controversial_ideas
|
||||
Description: Identify and analyze contentious viewpoints, challenging assumptions, and debatable claims from content while maintaining objective analysis.
|
||||
Tags: EXTRACTION, ANALYSIS, RESEARCH, CRITICAL THINKING
|
||||
|
||||
## Pattern: extract_ctf_writeup
|
||||
Description: Extract key techniques, tools, and methodologies from CTF challenge writeups to create structured, educational security learning resources.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: extract_ideas
|
||||
Description: Extract and organize key concepts, innovative thoughts, and potential applications from content into structured, actionable idea collections.
|
||||
Tags: EXTRACTION, ANALYSIS, WISDOM, SELF
|
||||
|
||||
## Pattern: extract_insights_dm
|
||||
Description: Extract and organize key insights from direct messages or conversations, focusing on valuable learnings and actionable takeaways.
|
||||
Tags: EXTRACTION, ANALYSIS, SUMMARIZATION, SELF, WISDOM
|
||||
|
||||
## Pattern: extract_instructions
|
||||
Description: Extract and organize step-by-step procedures and guidelines from content into clear, sequential instructions for practical implementation.
|
||||
Tags: EXTRACTION, LEARNING, BUSINESS
|
||||
|
||||
## Pattern: extract_jokes
|
||||
Description: Extract and categorize humorous content from text, organizing jokes, puns, and witty remarks while preserving their comedic timing and context.
|
||||
Tags: OTHER
|
||||
|
||||
## Pattern: extract_latest_video
|
||||
Description: Extract and summarize key information from the most recent video in a channel or playlist, including title, timestamp, and main content points.
|
||||
Tags: EXTRACTION, SUMMARIZATION
|
||||
|
||||
## Pattern: extract_most_redeeming_thing
|
||||
Description: Identify and analyze the most positive or valuable aspect from content, even in challenging or critical contexts, to highlight constructive elements.
|
||||
Tags: ANALYSIS, SELF, WISDOM
|
||||
|
||||
## Pattern: extract_patterns
|
||||
Description: Identify and extract recurring patterns, themes, and methodologies from content to create reusable templates and systematic approaches.
|
||||
Tags: EXTRACTION, ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: extract_poc
|
||||
Description: Extract and document proof-of-concept demonstrations from technical content, including implementation steps, code samples, and validation methods.
|
||||
Tags: DEVELOPMENT, BUSINESS
|
||||
|
||||
## Pattern: extract_primary_problem
|
||||
Description: Identify and analyze the core problem or challenge from content, focusing on root causes and fundamental issues rather than symptoms.
|
||||
Tags: ANALYSIS, EXTRACTION, CRITICAL THINKING
|
||||
|
||||
## Pattern: extract_primary_solution
|
||||
Description: Identify and analyze the main solution or approach proposed in content, focusing on key implementation steps and expected outcomes.
|
||||
Tags: ANALYSIS, EXTRACTION
|
||||
|
||||
## Pattern: extract_product_features
|
||||
Description: Extract and categorize key product features, capabilities, and specifications from content into a structured, comprehensive feature list.
|
||||
Tags: EXTRACTION, BUSINESS, DEVELOPMENT
|
||||
|
||||
## Pattern: extract_questions
|
||||
Description: Extract and categorize key questions, inquiries, and discussion points from content to create comprehensive Q&A resources.
|
||||
Tags: EXTRACTION, LEARNING, BUSINESS
|
||||
|
||||
## Pattern: extract_recipe
|
||||
Description: Extract and format cooking recipes from content into structured instructions with ingredients, steps, and preparation details.
|
||||
Tags: EXTRACTION, WRITING, CONVERSION, SELF
|
||||
|
||||
## Pattern: extract_recommendations
|
||||
Description: Extract and prioritize suggested actions, advice, and recommendations from content, organizing them into clear, actionable guidance.
|
||||
Tags: EXTRACTION, ANALYSIS, SUMMARIZATION, SELF, WISDOM
|
||||
|
||||
## Pattern: extract_references
|
||||
Description: Extract and format citations, sources, and bibliographic references from content into a structured, properly formatted reference list.
|
||||
Tags: EXTRACTION, RESEARCH, WRITING, LEARNING
|
||||
|
||||
## Pattern: extract_song_meaning
|
||||
Description: Analyze and interpret song lyrics to uncover deeper meanings, themes, metaphors, and cultural or historical references within musical compositions.
|
||||
Tags: ANALYSIS, SELF
|
||||
|
||||
## Pattern: extract_sponsors
|
||||
Description: Extract and organize sponsorship information from content, including sponsor names, promotional messages, and partnership details.
|
||||
Tags: EXTRACTION, BUSINESS
|
||||
|
||||
## Pattern: extract_videoid
|
||||
Description: Extract and parse video identifiers and URLs from content to create structured lists of video references and links.
|
||||
Tags: EXTRACTION, CONVERSION
|
||||
|
||||
## Pattern: extract_wisdom_agents
|
||||
Description: Extract and synthesize insights from AI agent interactions, focusing on emergent behaviors, learning patterns, and decision-making processes.
|
||||
Tags: AI, ANALYSIS, EXTRACTION
|
||||
|
||||
## Pattern: extract_wisdom_dm
|
||||
Description: Extract and analyze key insights and learnings from direct message conversations, focusing on personal growth and practical wisdom.
|
||||
Tags: EXTRACTION, ANALYSIS, SUMMARIZATION, SELF, WISDOM
|
||||
|
||||
## Pattern: extract_wisdom_nometa
|
||||
Description: Extract pure insights and wisdom from content without metadata or contextual annotations, focusing on core principles and essential truths.
|
||||
Tags: EXTRACTION, ANALYSIS, CRITICAL THINKING, WISDOM
|
||||
|
||||
## Pattern: find_hidden_message
|
||||
Description: Analyze content to uncover concealed meanings, subtle implications, and embedded messages that may not be immediately apparent.
|
||||
Tags: ANALYSIS, RESEARCH, CRITICAL THINKING
|
||||
|
||||
## Pattern: find_logical_fallacies
|
||||
Description: Identify and analyze logical errors, reasoning flaws, and argumentative fallacies in content to evaluate argument validity and soundness.
|
||||
Tags: ANALYSIS, RESEARCH, CRITICAL THINKING
|
||||
|
||||
## Pattern: get_wow_per_minute
|
||||
Description: Calculate and analyze the frequency of impressive or surprising moments in content to measure engagement and impact density.
|
||||
Tags: ANALYSIS, REVIEW
|
||||
|
||||
## Pattern: get_youtube_rss
|
||||
Description: Generate and format RSS feed URLs for YouTube channels and playlists to enable automated content tracking and updates.
|
||||
Tags: CONVERSION, DEVELOPMENT
|
||||
|
||||
## Pattern: humanize
|
||||
Description: Transform technical or complex content into more approachable, relatable language while maintaining accuracy and key information.
|
||||
Tags: WRITING, CONVERSION
|
||||
|
||||
## Pattern: identify_dsrp_distinctions
|
||||
Description: Analyze content using DSRP framework to identify key distinctions, differences, and unique characteristics between concepts and ideas.
|
||||
Tags: ANALYSIS, RESEARCH
|
||||
|
||||
## Pattern: identify_dsrp_perspectives
|
||||
Description: Analyze content using DSRP framework to identify different viewpoints, stakeholder perspectives, and alternative ways of understanding concepts.
|
||||
Tags: ANALYSIS, RESEARCH
|
||||
|
||||
## Pattern: identify_dsrp_relationships
|
||||
Description: Analyze content using DSRP framework to identify connections, correlations, and causal relationships between different concepts and elements.
|
||||
Tags: ANALYSIS, RESEARCH
|
||||
|
||||
## Pattern: identify_dsrp_systems
|
||||
Description: Analyze content using DSRP framework to identify systems, hierarchies, and organizational structures that connect different components and ideas.
|
||||
Tags: ANALYSIS, RESEARCH
|
||||
|
||||
## Pattern: identify_job_stories
|
||||
Description: Extract and analyze user job stories from content to understand core motivations, situational contexts, and desired outcomes in product usage.
|
||||
Tags: ANALYSIS, BUSINESS, DEVELOPMENT
|
||||
|
||||
## Pattern: improve_academic_writing
|
||||
Description: Enhance academic writing by improving clarity, structure, argumentation, and scholarly tone while maintaining rigorous academic standards.
|
||||
Tags: WRITING, RESEARCH
|
||||
|
||||
## Pattern: improve_prompt
|
||||
Description: Enhance AI prompts by refining clarity, specificity, and structure to generate more accurate, relevant, and useful responses.
|
||||
Tags: AI, WRITING, DEVELOPMENT
|
||||
|
||||
## Pattern: improve_report_finding
|
||||
Description: Enhance security report findings by improving clarity, technical accuracy, risk assessment, and remediation recommendations.
|
||||
Tags: SECURITY
|
||||
|
||||
## Pattern: improve_writing
|
||||
Description: Enhance written content by improving clarity, flow, structure, and style while maintaining the original message and intent.
|
||||
Tags: WRITING
|
||||
|
||||
## Pattern: judge_output
|
||||
Description: Evaluate AI-generated outputs for quality, accuracy, relevance, and adherence to specified requirements and standards.
|
||||
Tags: AI, ANALYSIS, REVIEW
|
||||
|
||||
## Pattern: label_and_rate
|
||||
Description: Categorize and evaluate content by assigning descriptive labels and numerical ratings based on defined criteria and quality metrics.
|
||||
Tags: ANALYSIS, REVIEW, WRITING
|
||||
|
||||
## Pattern: md_callout
|
||||
Description: Generate markdown callout blocks to highlight important information, warnings, notes, and tips with appropriate styling and formatting.
|
||||
Tags: WRITING, CONVERSION
|
||||
|
||||
## Pattern: official_pattern_template
|
||||
Description: Define standardized pattern templates with structured sections for identity, purpose, steps, and output specifications to ensure consistent pattern creation.
|
||||
Tags: DEVELOPMENT, WRITING
|
||||
|
||||
## Pattern: prepare_7s_strategy
|
||||
Description: Apply McKinsey's 7S framework to analyze organizational alignment across strategy, structure, systems, shared values, style, staff, and skills.
|
||||
Tags: ANALYSIS, BUSINESS, STRATEGY
|
||||
|
||||
## Pattern: provide_guidance
|
||||
Description: Offer expert advice and recommendations tailored to specific situations, providing clear, actionable steps and best practices for implementation.
|
||||
Tags: ANALYSIS, LEARNING, SELF
|
||||
|
||||
## Pattern: rate_ai_response
|
||||
Description: Evaluate AI responses for quality, coherence, relevance, and effectiveness, providing detailed scoring and improvement suggestions.
|
||||
Tags: AI, ANALYSIS, REVIEW
|
||||
|
||||
## Pattern: rate_ai_result
|
||||
Description: Assess AI-generated outputs against predefined success criteria, providing quantitative scores and qualitative feedback for improvement.
|
||||
Tags: AI, ANALYSIS, REVIEW
|
||||
|
||||
## Pattern: rate_content
|
||||
Description: Evaluate content quality across multiple dimensions including accuracy, clarity, engagement, and value, providing comprehensive scoring with detailed justification.
|
||||
Tags: ANALYSIS, REVIEW, WRITING
|
||||
|
||||
|
||||
## Pattern: rate_value
|
||||
Description: Assess the practical value and impact potential of content by evaluating its utility, applicability, and potential return on investment.
|
||||
Tags: ANALYSIS, BUSINESS, REVIEW
|
||||
|
||||
## Pattern: raw_query
|
||||
Description: Process direct, unstructured queries by interpreting intent and providing focused, relevant responses without additional formatting or templates.
|
||||
Tags: AI, ANALYSIS
|
||||
|
||||
## Pattern: recommend_artists
|
||||
Description: Suggest relevant artists and creators based on user preferences, style similarities, and thematic connections across various artistic mediums.
|
||||
Tags: ANALYSIS, RESEARCH, SELF
|
||||
|
||||
## Pattern: recommend_pipeline_upgrades
|
||||
Description: Analyze CI/CD pipelines and suggest improvements for efficiency, reliability, and security based on industry best practices and modern tooling.
|
||||
Tags: DEVELOPMENT, SECURITY
|
||||
|
||||
## Pattern: recommend_talkpanel_topics
|
||||
Description: Generate engaging discussion topics and questions for panel talks based on audience interests, current trends, and speaker expertise.
|
||||
Tags: ANALYSIS, WRITING
|
||||
|
||||
## Pattern: refine_design_document
|
||||
Description: Enhance software design documentation by improving clarity, completeness, technical accuracy, and alignment with architectural best practices.
|
||||
Tags: DEVELOPMENT, WRITING
|
||||
|
||||
## Pattern: review_design
|
||||
Description: Evaluate software design proposals and architectures for scalability, maintainability, security, and adherence to design principles and patterns.
|
||||
Tags: DEVELOPMENT, ANALYSIS, REVIEW
|
||||
|
||||
## Pattern: sanitize_broken_html_to_markdown
|
||||
Description: Clean and convert malformed HTML content into well-structured markdown format while preserving content hierarchy and semantic meaning.
|
||||
Tags: CONVERSION, DEVELOPMENT
|
||||
|
||||
## Pattern: show_fabric_options_markmap
|
||||
Description: Generate hierarchical mind maps visualizing Fabric framework capabilities, patterns, and configuration options using Markmap syntax.
|
||||
Tags: VISUALIZATION, DEVELOPMENT
|
||||
|
||||
## Pattern: solve_with_cot
|
||||
Description: Solve complex problems using chain-of-thought reasoning, breaking down solutions into clear, logical steps with explicit intermediate reasoning.
|
||||
Tags: AI, ANALYSIS, LEARNING
|
||||
|
||||
## Pattern: suggest_pattern
|
||||
Description: Recommend appropriate Fabric patterns based on user requirements, task characteristics, and desired outcomes to optimize pattern selection.
|
||||
Tags: AI, ANALYSIS, DEVELOPMENT
|
||||
|
||||
## Pattern: summarize
|
||||
Description: Generate comprehensive content summaries that capture key points, main arguments, and essential details while maintaining context and clarity.
|
||||
Tags: SUMMARIZATION, WRITING
|
||||
|
||||
## Pattern: summarize_debate
|
||||
Description: Create structured summaries of debates highlighting key arguments, points of agreement, areas of contention, and notable exchanges between participants.
|
||||
Tags: SUMMARIZATION, ANALYSIS, CRITICAL THINKING
|
||||
|
||||
## Pattern: summarize_git_changes
|
||||
Description: Generate clear, concise summaries of git repository changes, highlighting key modifications, additions, and deletions across commits.
|
||||
Tags: DEVELOPMENT, SUMMARIZATION
|
||||
|
||||
## Pattern: summarize_git_diff
|
||||
Description: Analyze git diff output to provide clear, structured summaries of code changes, highlighting functional modifications and their potential impact.
|
||||
Tags: DEVELOPMENT, ANALYSIS
|
||||
|
||||
## Pattern: summarize_lecture
|
||||
Description: Create structured summaries of academic lectures capturing key concepts, examples, methodologies, and important takeaways in an organized format.
|
||||
Tags: SUMMARIZATION, LEARNING, WRITING
|
||||
|
||||
## Pattern: summarize_legislation
|
||||
Description: Create comprehensive summaries of legislative documents highlighting key provisions, amendments, implications, and affected stakeholders.
|
||||
Tags: SUMMARIZATION, ANALYSIS, WRITING
|
||||
|
||||
## Pattern: summarize_meeting
|
||||
Description: Create structured meeting summaries capturing key discussions, decisions, action items, and assigned responsibilities in a clear, organized format.
|
||||
Tags: SUMMARIZATION, WRITING, BUSINESS
|
||||
|
||||
## Pattern: summarize_micro
|
||||
Description: Generate ultra-concise content summaries with one-sentence overview, key points, and essential takeaways in minimal words.
|
||||
Tags: SUMMARIZATION, WRITING
|
||||
|
||||
## Pattern: summarize_newsletter
|
||||
Description: Create concise summaries of newsletter content highlighting key updates, announcements, trends, and notable developments in a structured format.
|
||||
Tags: SUMMARIZATION, WRITING
|
||||
|
||||
## Pattern: summarize_paper
|
||||
Description: Create structured summaries of academic papers highlighting research objectives, methodology, key findings, and significant conclusions in a clear format.
|
||||
Tags: SUMMARIZATION, RESEARCH, WRITING, LEARNING
|
||||
|
||||
## Pattern: summarize_prompt
|
||||
Description: Analyze and summarize AI prompts to identify key instructions, requirements, constraints, and expected outputs in a clear, structured format.
|
||||
Tags: ANALYSIS, AI
|
||||
|
||||
## Pattern: summarize_pull-requests
|
||||
Description: Create concise summaries of pull requests highlighting key code changes, implementation details, and potential impacts in a clear, developer-friendly format.
|
||||
Tags: SUMMARIZATION, DEVELOPMENT
|
||||
|
||||
## Pattern: summarize_rpg_session
|
||||
Description: Create detailed summaries of roleplaying game sessions capturing key story events, character developments, combat encounters, and important decisions in a narrative format.
|
||||
Tags: SUMMARIZATION, GAMING, WRITING
|
||||
|
||||
## Pattern: t_analyze_challenge_handling
|
||||
Description: Evaluate approaches to handling challenges by analyzing response strategies, coping mechanisms, and problem-solving methods to identify effective patterns.
|
||||
Tags: ANALYSIS, STRATEGY, CRITICAL THINKING
|
||||
|
||||
## Pattern: t_check_metrics
|
||||
Description: Analyze and evaluate performance metrics, tracking progress against goals while identifying trends, patterns, and areas for improvement.
|
||||
Tags: ANALYSIS, BUSINESS
|
||||
|
||||
## Pattern: t_create_h3_career
|
||||
Description: Generate structured career development plans using the Head, Heart, Hands (H3) framework to align skills, passions, and practical actions.
|
||||
Tags: STRATEGY, BUSINESS, WRITING, SELF
|
||||
|
||||
## Pattern: t_create_opening_sentences
|
||||
Description: Generate compelling opening sentences for content that capture attention, establish context, and effectively introduce key themes or concepts.
|
||||
Tags: WRITING
|
||||
|
||||
## Pattern: t_describe_life_outlook
|
||||
Description: Analyze and articulate personal life philosophies, values, and worldviews to understand core beliefs and their influence on decision-making and behavior.
|
||||
Tags: ANALYSIS, WRITING, SELF
|
||||
|
||||
## Pattern: t_extract_intro_sentences
|
||||
Description: Extract and analyze introductory sentences from content to identify effective hooks, context-setting techniques, and engagement strategies.
|
||||
Tags: EXTRACTION, ANALYSIS, WRITING
|
||||
|
||||
## Pattern: t_extract_panel_topics
|
||||
Description: Extract and organize potential discussion topics from content to create engaging panel discussions, identifying key themes, controversies, and audience-relevant points.
|
||||
Tags: EXTRACTION, ANALYSIS, WRITING
|
||||
|
||||
## Pattern: t_find_blindspots
|
||||
Description: Identify and analyze potential blind spots in thinking, planning, or decision-making processes to uncover overlooked factors and improve strategic awareness.
|
||||
Tags: ANALYSIS, STRATEGY, CRITICAL THINKING
|
||||
|
||||
## Pattern: t_find_negative_thinking
|
||||
Description: Identify and analyze patterns of negative thinking in content to recognize cognitive distortions, self-limiting beliefs, and opportunities for reframing.
|
||||
Tags: ANALYSIS, STRATEGY, CRITICAL THINKING
|
||||
|
||||
## Pattern: t_find_neglected_goals
|
||||
Description: Identify and analyze goals, aspirations, or objectives that have been overlooked or deprioritized to surface opportunities for renewed focus and action.
|
||||
Tags: ANALYSIS, STRATEGY, CRITICAL THINKING, SELF
|
||||
|
||||
## Pattern: t_give_encouragement
|
||||
Description: Generate personalized, context-aware messages of encouragement that acknowledge challenges while reinforcing strengths and promoting resilience.
|
||||
Tags: WRITING, SELF
|
||||
|
||||
## Pattern: t_red_team_thinking
|
||||
Description: Apply adversarial thinking to analyze plans, systems, or ideas by identifying potential weaknesses, attack vectors, and failure modes to improve resilience.
|
||||
Tags: ANALYSIS, SECURITY, STRATEGY, CRITICAL THINKING
|
||||
|
||||
## Pattern: t_threat_model_plans
|
||||
Description: Analyze plans and strategies through a security lens to identify potential threats, vulnerabilities, and risks while providing mitigation recommendations.
|
||||
Tags: SECURITY, ANALYSIS, STRATEGY
|
||||
|
||||
## Pattern: t_visualize_mission_goals_projects
|
||||
Description: Create visual representations of organizational missions, strategic goals, and project hierarchies to clarify relationships and track progress toward objectives.
|
||||
Tags: VISUALIZATION, BUSINESS, STRATEGY
|
||||
|
||||
## Pattern: t_year_in_review
|
||||
Description: Generate comprehensive annual reviews by analyzing achievements, challenges, learnings, and growth opportunities while identifying patterns and setting future directions.
|
||||
Tags: ANALYSIS, WRITING, BUSINESS
|
||||
|
||||
## Pattern: to_flashcards
|
||||
Description: Convert educational content into structured flashcard format with clear questions and answers, optimized for spaced repetition learning.
|
||||
Tags: LEARNING, CONVERSION
|
||||
|
||||
## Pattern: transcribe_minutes
|
||||
Description: Convert meeting recordings or notes into structured, well-organized minutes capturing key discussions, decisions, action items, and attendee contributions.
|
||||
Tags: WRITING, BUSINESS, CONVERSION
|
||||
|
||||
## Pattern: translate
|
||||
Description: Convert content between languages while preserving meaning, context, and cultural nuances, ensuring accurate and natural-sounding translations.
|
||||
Tags: CONVERSION
|
||||
|
||||
## Pattern: tweet
|
||||
Description: Transform content into concise, engaging tweets that capture key messages while adhering to platform constraints and social media best practices.
|
||||
Tags: WRITING, CONVERSION
|
||||
|
||||
## Pattern: write_essay
|
||||
Description: Create well-structured essays with clear thesis statements, supporting arguments, evidence, and conclusions while maintaining academic writing standards.
|
||||
Tags: WRITING, RESEARCH, LEARNING
|
||||
|
||||
## Pattern: write_hackerone_report
|
||||
Description: Create detailed vulnerability reports following HackerOne's format, including clear reproduction steps, impact analysis, and remediation recommendations.
|
||||
Tags: SECURITY, WRITING, ANALYSIS
|
||||
|
||||
## Pattern: write_latex
|
||||
Description: Generate professional LaTeX documents with proper formatting, mathematical notation, citations, and cross-references following academic publishing standards.
|
||||
Tags: WRITING, RESEARCH, CONVERSION
|
||||
|
||||
## Pattern: write_micro_essay
|
||||
Description: Create concise, focused essays that present a single key idea with supporting evidence and analysis in a highly condensed format.
|
||||
Tags: WRITING, RESEARCH
|
||||
|
||||
## Pattern: write_nuclei_template_rule
|
||||
Description: Generate Nuclei vulnerability scanning templates with detection logic, payload patterns, and validation rules following the template syntax specification.
|
||||
Tags: SECURITY, DEVELOPMENT
|
||||
|
||||
## Pattern: write_pull-request
|
||||
Description: Create comprehensive pull request descriptions with clear summaries of changes, implementation details, testing procedures, and related issue references.
|
||||
Tags: DEVELOPMENT
|
||||
|
||||
## Pattern: write_semgrep_rule
|
||||
Description: Create Semgrep pattern matching rules for static code analysis, including detection patterns, metadata, and fix suggestions following the rule syntax specification.
|
||||
Tags: SECURITY, DEVELOPMENT
|
||||
@@ -0,0 +1,140 @@
|
||||
# Plan for Implementing Qualifier Support in Web Interface
|
||||
|
||||
## Current Issue
|
||||
The web interface currently treats qualifiers (like -g=fr) as part of the message text instead of processing them as command flags.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### 1. Add Qualifier Interface
|
||||
```typescript
|
||||
// Add to chat-interface.ts
|
||||
export interface ChatQualifiers {
|
||||
language?: string; // -g flag
|
||||
temperature?: number; // -t flag
|
||||
topP?: number; // -T flag
|
||||
presencePenalty?: number; // -P flag
|
||||
frequencyPenalty?: number; // -F flag
|
||||
raw?: boolean; // -r flag
|
||||
model?: string; // -m flag
|
||||
}
|
||||
|
||||
// Update ChatRequest to include qualifiers
|
||||
export interface ChatRequest {
|
||||
prompts: ChatPrompt[];
|
||||
messages: Message[];
|
||||
qualifiers?: ChatQualifiers;
|
||||
// ... existing fields
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add Qualifier Parser
|
||||
```typescript
|
||||
// New file: src/lib/utils/qualifier-parser.ts
|
||||
export function parseQualifiers(input: string): {
|
||||
text: string;
|
||||
qualifiers: ChatQualifiers;
|
||||
} {
|
||||
const qualifierRegex = /-([a-zA-Z]+)(?:=([^\s]+))?/g;
|
||||
const qualifiers: ChatQualifiers = {};
|
||||
|
||||
// Remove qualifiers and get clean text
|
||||
const text = input.replace(qualifierRegex, (match, flag, value) => {
|
||||
switch (flag) {
|
||||
case 'g':
|
||||
qualifiers.language = value;
|
||||
break;
|
||||
case 't':
|
||||
qualifiers.temperature = parseFloat(value);
|
||||
break;
|
||||
// ... handle other qualifiers
|
||||
}
|
||||
return '';
|
||||
}).trim();
|
||||
|
||||
return { text, qualifiers };
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Modify ChatInput Component
|
||||
```typescript
|
||||
// In ChatInput.svelte
|
||||
import { parseQualifiers } from '$lib/utils/qualifier-parser';
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
|
||||
try {
|
||||
const { text, qualifiers } = parseQualifiers(userInput);
|
||||
|
||||
// Add qualifiers to request
|
||||
const request = await chatService.createChatRequest(text);
|
||||
request.qualifiers = qualifiers;
|
||||
|
||||
// Send to backend
|
||||
const stream = await chatService.streamChat(request);
|
||||
// ... rest of the code
|
||||
} catch (error) {
|
||||
// ... error handling
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update ChatService
|
||||
```typescript
|
||||
// In ChatService.ts
|
||||
public async createChatRequest(userInput: string): Promise<ChatRequest> {
|
||||
const config = get(chatConfig);
|
||||
return {
|
||||
prompts: [{
|
||||
userInput,
|
||||
systemPrompt: get(systemPrompt),
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName)
|
||||
}],
|
||||
messages: get(messageStore),
|
||||
...config
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Update Backend API
|
||||
The backend API at localhost:8080/api/chat already supports these qualifiers, so we just need to ensure they're properly included in the request body.
|
||||
|
||||
## Testing Plan
|
||||
|
||||
1. Test Basic Qualifier Parsing:
|
||||
```typescript
|
||||
const input = "hello -g=fr -t=0.8";
|
||||
const { text, qualifiers } = parseQualifiers(input);
|
||||
// Should return:
|
||||
// text: "hello"
|
||||
// qualifiers: { language: "fr", temperature: 0.8 }
|
||||
```
|
||||
|
||||
2. Test Multiple Qualifiers:
|
||||
```typescript
|
||||
const input = "-g=fr -t=0.8 -P=0.2 hello world";
|
||||
// Should parse all qualifiers and extract clean text
|
||||
```
|
||||
|
||||
3. Test Invalid Qualifiers:
|
||||
```typescript
|
||||
const input = "hello -g=fr -invalid=123";
|
||||
// Should ignore invalid qualifiers
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Implement the qualifier parser
|
||||
2. Update the chat interfaces
|
||||
3. Modify ChatInput to use the parser
|
||||
4. Add error handling for invalid qualifiers
|
||||
5. Add validation for qualifier values
|
||||
6. Update documentation
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. Add UI controls for common qualifiers
|
||||
2. Add autocomplete for qualifier flags
|
||||
3. Add validation feedback for invalid qualifiers
|
||||
4. Add help text showing available qualifiers
|
||||
131
web/myfiles/Obsidian_perso_not_share/rebased_fabric-contrib.md
Normal file
131
web/myfiles/Obsidian_perso_not_share/rebased_fabric-contrib.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Fabric Web UI Enhancement Contribution Setup
|
||||
|
||||
### One-Time Setup in VSCode
|
||||
|
||||
Before adding web UI enhancements:
|
||||
|
||||
1. Verify working branch:
|
||||
```bash
|
||||
git status
|
||||
|
||||
Ensures active branch is feature/web-ui-enhancements for isolated development
|
||||
|
||||
Check repository connections:
|
||||
git fetch upstream
|
||||
git status
|
||||
|
||||
Validates:
|
||||
|
||||
Clean working tree
|
||||
Latest upstream code
|
||||
Branch synchronization status
|
||||
Pending changes if any
|
||||
|
||||
Verify remote configurations:
|
||||
git remote -v
|
||||
|
||||
Shows all configured remotes:
|
||||
|
||||
origin: Your fork (jmd1010/fabricDM)
|
||||
upstream: Original repo (danielmiessler/fabric)
|
||||
backup: Full environment (jmd1010/fabric-backup)
|
||||
|
||||
This verification process:
|
||||
|
||||
Establishes clean development foundation
|
||||
Confirms proper repository relationships
|
||||
Enables isolated feature development
|
||||
Maintains clear upgrade path
|
||||
Preserves complete backup access
|
||||
|
||||
|
||||
This documentation provides clear steps for initial VSCode setup while maintaining proper Git workflow and repository relationships.
|
||||
|
||||
|
||||
|
||||
## Repository Structure
|
||||
- Fork: jmd1010/fabric-contrib
|
||||
- Original: danielmiessler/fabric
|
||||
- Branch: feature/web-ui-enhancements
|
||||
|
||||
## Remote Configuration
|
||||
- origin: git@github.com:jmd1010/fabricDM.git
|
||||
- upstream: https://github.com/danielmiessler/fabric.git
|
||||
|
||||
## Development Environment
|
||||
### Shell Configurations
|
||||
- Updated .zshrc and .bashrc
|
||||
- Corrected paths for new repository structure
|
||||
- Go environment variables
|
||||
- Pattern directory mappings
|
||||
|
||||
### Web Development Setup
|
||||
- Backend: fabric --serve
|
||||
- Frontend: npm run dev in web directory
|
||||
- Development URLs and ports
|
||||
|
||||
## Pull Request Workflow
|
||||
1. Sync with upstream
|
||||
2. Feature branch development
|
||||
3. Code review preparation
|
||||
4. PR submission guidelines
|
||||
|
||||
## Backup Management
|
||||
### Repository Setup
|
||||
- Backup: git@github.com:jmd1010/fabric-backup.git
|
||||
- Contains complete development environment
|
||||
- Excludes only sensitive files (.env)
|
||||
- Private repository for safety
|
||||
|
||||
### Backup Workflow
|
||||
1. Regular backup pushes:
|
||||
```bash
|
||||
git push backup feature/web-ui-enhancements
|
||||
|
||||
2. After significant changes:
|
||||
|
||||
git add .
|
||||
git commit -m "Development checkpoint - [description]"
|
||||
git push backup feature/web-ui-enhancements
|
||||
|
||||
3. Before major refactoring:
|
||||
|
||||
git tag backup-pre-refactor-[date]
|
||||
git push backup --tags
|
||||
|
||||
|
||||
Development vs Backup
|
||||
|
||||
Backup repo: Complete environment preservation
|
||||
|
||||
Feature branch: Clean commits for PR
|
||||
|
||||
Separate commit messages for backup vs PR
|
||||
|
||||
Tag significant development points
|
||||
|
||||
Regular synchronization with both repos
|
||||
|
||||
Recovery Procedures
|
||||
|
||||
1. From backup:
|
||||
|
||||
git clone git@github.com:jmd1010/fabric-backup.git
|
||||
git checkout feature/web-ui-enhancements
|
||||
|
||||
2. Environment restoration:
|
||||
|
||||
Copy .env.example to .env
|
||||
Configure local paths
|
||||
Install dependencies
|
||||
Verify development setup
|
||||
|
||||
|
||||
This structure provides:
|
||||
|
||||
1. Clear separation of concerns
|
||||
2. Detailed workflow procedures
|
||||
3. Recovery instructions
|
||||
4. Best practices for both backup and development
|
||||
5. Easy reference for ongoing work
|
||||
|
||||
56
web/myfiles/Obsidian_perso_not_share/restoration-complete.md
Normal file
56
web/myfiles/Obsidian_perso_not_share/restoration-complete.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Restoration to Pre-1:33 PM State Complete
|
||||
|
||||
## Files Restored
|
||||
1. Core Files:
|
||||
- chat-interface.ts (1:31:45 PM)
|
||||
- ChatService.ts (1:32:10 PM)
|
||||
- chat-store.ts (1:31:55 PM)
|
||||
- ChatInput.svelte (1:32:15 PM)
|
||||
- ChatMessages.svelte (1:32:05 PM)
|
||||
- +server.ts (1:32:24 PM)
|
||||
|
||||
2. Language Support:
|
||||
- language-store.ts (1:32:20 PM)
|
||||
- language-options.md (documentation)
|
||||
|
||||
3. Session Management:
|
||||
- base.ts (removed streaming)
|
||||
- file-utils.ts (basic file operations)
|
||||
- session-store.ts (basic session management)
|
||||
- SessionManager.svelte (basic UI without copy)
|
||||
|
||||
4. UI Components:
|
||||
- ModelConfig.svelte (no changes needed)
|
||||
- select.svelte (UI library)
|
||||
- Tooltip.svelte (UI library)
|
||||
|
||||
## Files Kept
|
||||
- raw-store.ts (existed before 1:33)
|
||||
|
||||
## Files Deleted
|
||||
- stream-store.ts
|
||||
- clipboard.ts
|
||||
- copy-store.ts
|
||||
- qualifier-store.ts
|
||||
- QualifierInput.svelte
|
||||
|
||||
## Documentation Files
|
||||
- stream-lessons.md
|
||||
- changes-since-133pm.md
|
||||
- files-after-133pm.md
|
||||
|
||||
## Working Features
|
||||
1. Language Support
|
||||
- --fr/--en qualifiers in ChatInput
|
||||
- Language instruction added in ChatService
|
||||
- Language state managed in language-store
|
||||
|
||||
2. Session Management
|
||||
- Save/Load sessions
|
||||
- Clear chat
|
||||
- Revert last message
|
||||
|
||||
3. Core Chat
|
||||
- Message sending/receiving
|
||||
- Markdown rendering
|
||||
- Basic UI layout
|
||||
98
web/myfiles/Obsidian_perso_not_share/stream-lessons.md
Normal file
98
web/myfiles/Obsidian_perso_not_share/stream-lessons.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Streaming Implementation Lessons
|
||||
|
||||
## What We Tried
|
||||
|
||||
1. Direct Event Stream Handling:
|
||||
- Added StreamResponse type to interfaces
|
||||
- Modified server to parse event stream data
|
||||
- Added streaming state to stores
|
||||
- Created dedicated stream-store.ts
|
||||
- Modified UI components for streaming display
|
||||
|
||||
2. Implementation Issues:
|
||||
- Mixed concerns between streaming and response handling
|
||||
- Added complexity to multiple components
|
||||
- Created tight coupling between components
|
||||
- Made error handling more complex
|
||||
- Lost the simplicity of the working language implementation
|
||||
|
||||
## What We Learned
|
||||
|
||||
1. Architecture Issues:
|
||||
- Streaming should be handled at a lower level
|
||||
- Response format should be consistent regardless of streaming
|
||||
- UI should be agnostic to streaming mode
|
||||
- State management became too complex
|
||||
- Too many components were modified
|
||||
|
||||
2. Better Approach Would Be:
|
||||
|
||||
a. Server Layer:
|
||||
- Handle streaming at the transport level
|
||||
- Abstract streaming details from response format
|
||||
- Keep consistent response structure
|
||||
- Handle errors at the boundary
|
||||
|
||||
b. Service Layer:
|
||||
- Use a streaming adapter pattern
|
||||
- Keep core service logic unchanged
|
||||
- Handle streaming as a separate concern
|
||||
- Maintain backward compatibility
|
||||
|
||||
c. Store Layer:
|
||||
- Keep stores focused on data, not transport
|
||||
- Use message queue pattern for updates
|
||||
- Maintain simple state management
|
||||
- Avoid streaming-specific stores
|
||||
|
||||
d. UI Layer:
|
||||
- Keep components transport-agnostic
|
||||
- Use progressive enhancement for streaming
|
||||
- Maintain simple update mechanism
|
||||
- Focus on display, not data handling
|
||||
|
||||
## Recommendations for Future Implementation
|
||||
|
||||
1. Architecture:
|
||||
- Create a streaming adapter layer
|
||||
- Keep core components unchanged
|
||||
- Use message queue for updates
|
||||
- Maintain separation of concerns
|
||||
|
||||
2. Response Format:
|
||||
- Use consistent format for streaming/non-streaming
|
||||
- Handle chunking at transport level
|
||||
- Keep message structure simple
|
||||
- Maintain type safety
|
||||
|
||||
3. Error Handling:
|
||||
- Handle streaming errors separately
|
||||
- Keep core error handling unchanged
|
||||
- Provide clear error boundaries
|
||||
- Maintain good user experience
|
||||
|
||||
4. Testing:
|
||||
- Test streaming in isolation
|
||||
- Maintain existing test coverage
|
||||
- Add streaming-specific tests
|
||||
- Ensure backward compatibility
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
1. Keep It Simple:
|
||||
- Don't mix streaming with core logic
|
||||
- Maintain clear boundaries
|
||||
- Use proven patterns
|
||||
- Think about maintainability
|
||||
|
||||
2. Separation of Concerns:
|
||||
- Transport layer handles streaming
|
||||
- Service layer stays clean
|
||||
- UI remains simple
|
||||
- Stores focus on data
|
||||
|
||||
3. Progressive Enhancement:
|
||||
- Start with working non-streaming version
|
||||
- Add streaming as enhancement
|
||||
- Keep fallback mechanism
|
||||
- Maintain compatibility
|
||||
59
web/myfiles/Obsidian_perso_not_share/update-pr-steps.md
Normal file
59
web/myfiles/Obsidian_perso_not_share/update-pr-steps.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Steps to Update Existing Pull Request with New Enhancements
|
||||
|
||||
## 1. Update Your FabricDM Repository
|
||||
```bash
|
||||
# Navigate to your FabricDM directory
|
||||
cd /path/to/FabricDM
|
||||
|
||||
# Make sure you're on your feature branch
|
||||
git checkout feature/your-improvements
|
||||
|
||||
# Pull any updates from upstream if needed
|
||||
git remote add upstream https://github.com/danielmiessler/fabric.git
|
||||
git fetch upstream
|
||||
git rebase upstream/main
|
||||
```
|
||||
|
||||
## 2. Copy New Changes
|
||||
Copy the following modified/new files from your Fabric2 repository to FabricDM:
|
||||
|
||||
### Pattern Search Feature:
|
||||
- `fabric/web/src/lib/components/patterns/PatternList.svelte`
|
||||
- `fabric/web/src/lib/components/ui/input/Input.svelte`
|
||||
|
||||
### Obsidian Integration:
|
||||
- Any modified files related to Obsidian integration
|
||||
|
||||
## 3. Update Documentation
|
||||
- Copy the enhanced PR description from `enhanced-pattern-selection-update.md` to document the new features
|
||||
|
||||
## 4. Commit and Push Changes
|
||||
```bash
|
||||
# Stage your changes
|
||||
git add .
|
||||
|
||||
# Commit with a descriptive message
|
||||
git commit -m "feat: add pattern search and Obsidian integration enhancements
|
||||
|
||||
- Added search functionality to pattern selection modal
|
||||
- Added Obsidian integration for pattern execution output
|
||||
- Updated documentation"
|
||||
|
||||
# Push to your feature branch
|
||||
git push origin feature/your-improvements
|
||||
```
|
||||
|
||||
## 5. Update Pull Request
|
||||
The push will automatically update your existing pull request. You should:
|
||||
1. Update the PR description with the new content from enhanced-pattern-selection-update.md
|
||||
2. Add a comment mentioning the new enhancements you've added
|
||||
3. Request a new review
|
||||
|
||||
## Benefits of This Approach
|
||||
1. Keeps all related improvements in one PR
|
||||
2. Maintains the context of your changes
|
||||
3. Makes it easier for reviewers to understand the full scope
|
||||
4. Avoids fragmenting the review process
|
||||
|
||||
## Note
|
||||
Make sure to test all functionality after copying files to ensure everything works correctly in the FabricDM repository.
|
||||
48
web/myfiles/Obsidian_perso_not_share/youtube-language-fix.md
Normal file
48
web/myfiles/Obsidian_perso_not_share/youtube-language-fix.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# YouTube Language Processing Fix - Analysis
|
||||
|
||||
## Current Working Implementation
|
||||
```typescript
|
||||
// In processYouTubeURL:
|
||||
|
||||
// Get language
|
||||
const currentLanguage = get(languageStore);
|
||||
|
||||
// Process stream with language instruction per chunk
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content: string, response?: StreamResponse) => {
|
||||
// Add language instruction to each chunk
|
||||
if (currentLanguage !== 'en') {
|
||||
content = `${content}. Please use the language '${currentLanguage}' for the output.`;
|
||||
}
|
||||
// Update messages...
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Why This Works
|
||||
1. YouTube transcripts are long and processed in chunks
|
||||
2. Each chunk gets its own language instruction
|
||||
3. Model maintains language context throughout
|
||||
4. No chance of language being "forgotten" mid-stream
|
||||
|
||||
## Key Points
|
||||
1. Adding language at start would only affect first chunk
|
||||
2. Long transcripts need language reinforcement
|
||||
3. Current implementation ensures consistent language
|
||||
4. Works with streaming nature of processing
|
||||
|
||||
## Verification
|
||||
1. Language instruction added to each chunk
|
||||
2. Pattern output stays in correct language
|
||||
3. No language switching mid-stream
|
||||
4. Consistent output throughout
|
||||
|
||||
## Conclusion
|
||||
The current implementation should be kept as is because:
|
||||
1. It's proven to work in practice
|
||||
2. Handles chunked processing correctly
|
||||
3. Maintains language context
|
||||
4. Produces consistent translations
|
||||
|
||||
No changes needed since the current approach successfully handles YouTube language processing.
|
||||
0
web/myfiles/inbox/.gitkeep
Normal file
0
web/myfiles/inbox/.gitkeep
Normal file
15
web/src/lib/actions/clickOutside.ts
Normal file
15
web/src/lib/actions/clickOutside.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export function clickOutside(node: HTMLElement, handler: () => void) {
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (node && !node.contains(event.target as Node) && !event.defaultPrevented) {
|
||||
handler();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,24 +2,113 @@
|
||||
import ChatInput from "./ChatInput.svelte";
|
||||
import ChatMessages from "./ChatMessages.svelte";
|
||||
import ModelConfig from "./ModelConfig.svelte";
|
||||
import Models from "./Models.svelte";
|
||||
import Patterns from "./Patterns.svelte";
|
||||
import DropdownGroup from "./DropdownGroup.svelte";
|
||||
import NoteDrawer from "$lib/components/ui/noteDrawer/NoteDrawer.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Checkbox } from "$lib/components/ui/checkbox";
|
||||
import Tooltip from "$lib/components/ui/tooltip/Tooltip.svelte";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { obsidianSettings } from "$lib/store/obsidian-store";
|
||||
import { featureFlags } from "$lib/config/features";
|
||||
import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { systemPrompt, selectedPatternName } from "$lib/store/pattern-store";
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
function openDrawer() {
|
||||
drawerStore.open({});
|
||||
}
|
||||
|
||||
$: showObsidian = $featureFlags.enableObsidianIntegration;
|
||||
</script>
|
||||
|
||||
<div class="flex gap-4 p-2 w-full">
|
||||
<aside class="w-1/5">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Patterns />
|
||||
<Models />
|
||||
<div class="flex gap-0 p-2 w-full h-screen">
|
||||
<!-- Left Column -->
|
||||
<aside class="w-[50%] flex flex-col gap-2 pr-2">
|
||||
<!-- Dropdowns Group -->
|
||||
<div class="bg-background/5 p-2 rounded-lg">
|
||||
<div class="rounded-lg bg-background/10">
|
||||
<DropdownGroup />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Config -->
|
||||
<div class="bg-background/5 p-2 rounded-lg">
|
||||
<ModelConfig />
|
||||
</div>
|
||||
|
||||
<!-- Message Input -->
|
||||
<div class="h-[200px] bg-background/5 rounded-lg overflow-hidden">
|
||||
<ChatInput />
|
||||
</div>
|
||||
|
||||
<!-- System Instructions -->
|
||||
<div class="flex-1 min-h-0 bg-background/5 p-2 rounded-lg">
|
||||
<div class="h-full flex flex-col">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
readonly={true}
|
||||
placeholder="System instructions will appear here when you select a pattern..."
|
||||
class="w-full flex-1 bg-primary-800/30 rounded-lg border-none whitespace-pre-wrap overflow-y-auto resize-none text-sm scrollbar-thin scrollbar-thumb-white/10 scrollbar-track-transparent hover:scrollbar-thumb-white/20"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="w-1/2">
|
||||
<ChatInput />
|
||||
</div>
|
||||
<!-- Right Column -->
|
||||
<div class="flex flex-col w-[50%] gap-2">
|
||||
<!-- Header with Obsidian Settings -->
|
||||
<div class="flex items-center justify-between px-2 py-1">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if showObsidian}
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<Checkbox
|
||||
bind:checked={$obsidianSettings.saveToObsidian}
|
||||
id="save-to-obsidian"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
<Label for="save-to-obsidian" class="text-xs text-white/70">Save to Obsidian</Label>
|
||||
</div>
|
||||
{#if $obsidianSettings.saveToObsidian}
|
||||
<Input
|
||||
id="note-name"
|
||||
bind:value={$obsidianSettings.noteName}
|
||||
placeholder="Note name..."
|
||||
class="h-6 text-xs w-48 bg-white/5 border-none focus:ring-1 ring-white/20"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" class="h-6 px-2 text-xs opacity-70 hover:opacity-100" on:click={openDrawer}>
|
||||
<Tooltip text="Take Notes" position="left">
|
||||
<span>Take Notes</span>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="w-1/2">
|
||||
<ChatMessages />
|
||||
<!-- Chat Area -->
|
||||
<div class="flex-1 flex flex-col min-h-0">
|
||||
<!-- Chat History -->
|
||||
<div class="flex-1 min-h-0 bg-background/5 rounded-lg overflow-hidden">
|
||||
<ChatMessages />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NoteDrawer />
|
||||
|
||||
<style>
|
||||
.loading-message {
|
||||
animation: flash 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,26 +2,88 @@
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { sendMessage, messageStore } from '$lib/store/chat-store';
|
||||
import { systemPrompt } from '$lib/store/pattern-store';
|
||||
import { systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { FileButton } from '@skeletonlabs/skeleton';
|
||||
import { Paperclip, Send, FileCheck } from 'lucide-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { getTranscript } from '$lib/services/transcriptService';
|
||||
import { ChatService } from '$lib/services/ChatService';
|
||||
import type { StreamResponse } from '$lib/interfaces/chat-interface';
|
||||
// import { obsidianSettings } from '$lib/store/obsidian-store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { obsidianSettings, updateObsidianSettings } from '$lib/store/obsidian-store';
|
||||
|
||||
const chatService = new ChatService();
|
||||
let userInput = "";
|
||||
//let files: FileList;
|
||||
let isYouTubeURL = false;
|
||||
const toastStore = getToastStore();
|
||||
|
||||
let files: File[] = [];
|
||||
let files: FileList | undefined = undefined;
|
||||
let uploadedFiles: string[] = [];
|
||||
let fileContents: string[] = [];
|
||||
let isProcessingFiles = false;
|
||||
|
||||
function detectYouTubeURL(input: string): boolean {
|
||||
const youtubePattern = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)/i;
|
||||
const isYoutube = youtubePattern.test(input);
|
||||
if (isYoutube) {
|
||||
console.log('YouTube URL detected:', input);
|
||||
console.log('Current system prompt:', $systemPrompt?.length);
|
||||
console.log('Selected pattern:', $selectedPatternName);
|
||||
}
|
||||
return isYoutube;
|
||||
}
|
||||
|
||||
function handleInput(event: Event) {
|
||||
console.log('\n=== Handle Input ===');
|
||||
const target = event.target as HTMLTextAreaElement;
|
||||
userInput = target.value;
|
||||
|
||||
const currentLanguage = get(languageStore);
|
||||
|
||||
const languageQualifiers = {
|
||||
'--en': 'en',
|
||||
'--fr': 'fr',
|
||||
'--es': 'es',
|
||||
'--de': 'de',
|
||||
'--zh': 'zh',
|
||||
'--ja': 'ja'
|
||||
};
|
||||
|
||||
let detectedLang = '';
|
||||
for (const [qualifier, lang] of Object.entries(languageQualifiers)) {
|
||||
if (userInput.includes(qualifier)) {
|
||||
detectedLang = lang;
|
||||
languageStore.set(lang);
|
||||
userInput = userInput.replace(new RegExp(`${qualifier}\\s*`), '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('2. Language state:', {
|
||||
previousLanguage: currentLanguage,
|
||||
currentLanguage: get(languageStore),
|
||||
detectedOverride: detectedLang,
|
||||
inputAfterLangRemoval: userInput
|
||||
});
|
||||
|
||||
isYouTubeURL = detectYouTubeURL(userInput);
|
||||
console.log('3. URL detection:', {
|
||||
isYouTube: isYouTubeURL,
|
||||
pattern: $selectedPatternName,
|
||||
systemPromptLength: $systemPrompt?.length
|
||||
});
|
||||
}
|
||||
|
||||
async function handleFileUpload(e: Event) {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
if (uploadedFiles.length >= 5 || (uploadedFiles.length + files.length) > 5) {
|
||||
toastStore.error('Maximum 5 files allowed');
|
||||
toastStore.trigger({
|
||||
message: 'Maximum 5 files allowed',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +96,10 @@
|
||||
uploadedFiles = [...uploadedFiles, file.name];
|
||||
}
|
||||
} catch (error) {
|
||||
toastStore.error('Error processing files: ' + error.message);
|
||||
toastStore.trigger({
|
||||
message: 'Error processing files: ' + (error as Error).message,
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
} finally {
|
||||
isProcessingFiles = false;
|
||||
}
|
||||
@@ -49,39 +114,165 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
async function saveToObsidian(content: string) {
|
||||
if (!$obsidianSettings.saveToObsidian) {
|
||||
console.log('Obsidian saving is disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$obsidianSettings.noteName) {
|
||||
toastStore.trigger({
|
||||
message: 'Please enter a note name in Obsidian settings',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$selectedPatternName) {
|
||||
toastStore.trigger({
|
||||
message: 'No pattern selected',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
toastStore.trigger({
|
||||
message: 'No content to save',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let finalContent = "";
|
||||
if (fileContents.length > 0) {
|
||||
finalContent += '\n\nFile Contents:\n' + fileContents.map((content, index) =>
|
||||
`[${uploadedFiles[index]}]:\n${content}`
|
||||
).join('\n\n');
|
||||
const response = await fetch('/obsidian', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
pattern: $selectedPatternName,
|
||||
noteName: $obsidianSettings.noteName,
|
||||
content
|
||||
})
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(responseData.error || 'Failed to save to Obsidian');
|
||||
}
|
||||
|
||||
const trimmedInput = userInput.trim() + '\n' + (finalContent || '');
|
||||
const trimmedSystemPrompt = $systemPrompt.trim();
|
||||
let messageHistory = JSON.stringify($messageStore);
|
||||
|
||||
$systemPrompt = "";
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
|
||||
// Place the messageHistory in the sendMessage(``) function to send the message history
|
||||
// This is a WIP and temporarily disabled.
|
||||
await sendMessage(`${trimmedSystemPrompt}\n${trimmedInput}\n${finalContent}`);
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
// Add this after successful save
|
||||
updateObsidianSettings({
|
||||
saveToObsidian: false, // Reset the save flag
|
||||
noteName: '' // Clear the note name
|
||||
});
|
||||
toastStore.trigger({
|
||||
message: 'Failed to send message. Please try again.',
|
||||
message: responseData.message || `Saved to Obsidian: ${responseData.fileName}`,
|
||||
background: 'variant-filled-success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to save to Obsidian:', error);
|
||||
toastStore.trigger({
|
||||
message: error instanceof Error ? error.message : 'Failed to save to Obsidian',
|
||||
background: 'variant-filled-error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
async function processYouTubeURL(input: string) {
|
||||
console.log('\n=== YouTube Flow Start ===');
|
||||
const originalLanguage = get(languageStore);
|
||||
|
||||
try {
|
||||
// Add processing message first
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: 'system',
|
||||
content: 'Processing YouTube video...',
|
||||
format: 'loading'
|
||||
}]);
|
||||
|
||||
// Get transcript but don't display it
|
||||
const { transcript } = await getTranscript(input);
|
||||
|
||||
// Process with current language and pattern
|
||||
const stream = await chatService.streamChat(transcript, $systemPrompt);
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content, response) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
// Replace the processing message with actual content
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
if (lastMessage?.format === 'loading') {
|
||||
newMessages.pop();
|
||||
}
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
|
||||
// Handle Obsidian saving if needed
|
||||
if ($obsidianSettings.saveToObsidian) {
|
||||
let lastContent = '';
|
||||
messageStore.subscribe(messages => {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastContent = lastMessage.content;
|
||||
}
|
||||
})();
|
||||
if (lastContent) await saveToObsidian(lastContent);
|
||||
}
|
||||
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
} catch (error) {
|
||||
console.error('Error processing YouTube URL:', error);
|
||||
messageStore.update(messages =>
|
||||
messages.filter(m => m.format !== 'loading')
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!userInput.trim()) return;
|
||||
|
||||
try {
|
||||
console.log('\n=== Submit Handler Start ===');
|
||||
|
||||
if (isYouTubeURL) {
|
||||
console.log('2a. Starting YouTube flow');
|
||||
await processYouTubeURL(userInput);
|
||||
return;
|
||||
}
|
||||
|
||||
const finalContent = fileContents.length > 0
|
||||
? userInput + '\n\nFile Contents:\n' + fileContents.join('\n\n')
|
||||
: userInput;
|
||||
|
||||
await sendMessage(finalContent);
|
||||
|
||||
userInput = "";
|
||||
uploadedFiles = [];
|
||||
fileContents = [];
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
@@ -94,60 +285,76 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 h-full">
|
||||
<div class="flex-1 relative shadow-lg">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
on:input={(e) => $systemPrompt || ''}
|
||||
placeholder="Enter system instructions..."
|
||||
class="w-full h-full resize-none bg-primary-800/30 rounded-lg border-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 relative shadow-lg">
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={(e) => userInput}
|
||||
on:keydown={handleKeydown}
|
||||
placeholder="Enter your message..."
|
||||
class="w-full h-full resize-none bg-primary-800/30 rounded-lg border-none"
|
||||
/>
|
||||
<div class="absolute bottom-5 right-2 gap-2 flex justify-end end-7">
|
||||
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn variant-default"
|
||||
bind:files
|
||||
on:change={handleFileUpload}
|
||||
disabled={isProcessingFiles || uploadedFiles.length >= 5}
|
||||
>
|
||||
{#if uploadedFiles.length > 0}
|
||||
<FileCheck class="w-4 h-4" />
|
||||
{:else}
|
||||
<Paperclip class="w-4 h-4" />
|
||||
{/if}
|
||||
</FileButton>
|
||||
<div class="h-full flex flex-col p-2">
|
||||
<div class="relative flex-1 min-h-0 bg-primary-800/30 rounded-lg">
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={handleInput}
|
||||
on:keydown={handleKeydown}
|
||||
placeholder="Enter your message (YouTube URLs will be automatically processed)..."
|
||||
class="w-full h-full resize-none bg-transparent border-none text-sm focus:ring-0 transition-colors p-3 pb-[48px]"
|
||||
/>
|
||||
<div class="absolute bottom-3 right-3 flex items-center gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
{#if uploadedFiles.length > 0}
|
||||
<span class="text-sm text-gray-500 space-x-2">
|
||||
<span class="text-xs text-white/70">
|
||||
{uploadedFiles.length} file{uploadedFiles.length > 1 ? 's' : ''} attached
|
||||
</span>
|
||||
{/if}
|
||||
<br>
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn-icon variant-ghost"
|
||||
bind:files
|
||||
on:change={handleFileUpload}
|
||||
disabled={isProcessingFiles || uploadedFiles.length >= 5}
|
||||
class="h-10 w-10 bg-primary-800/30 hover:bg-primary-800/50 rounded-full transition-colors"
|
||||
>
|
||||
{#if uploadedFiles.length > 0}
|
||||
<FileCheck class="w-5 h-5" />
|
||||
{:else}
|
||||
<Paperclip class="w-5 h-5" />
|
||||
{/if}
|
||||
</FileButton>
|
||||
<Button
|
||||
type="button"
|
||||
variant="default"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
name="send"
|
||||
on:click={handleSubmit}
|
||||
disabled={isProcessingFiles || !userInput.trim()}
|
||||
class="h-10 w-10 bg-primary-800/30 hover:bg-primary-800/50 rounded-full transition-colors disabled:opacity-30"
|
||||
>
|
||||
<Send class="w-4 h-4" />
|
||||
<Send class="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.flex-col {
|
||||
min-height: 0;
|
||||
}
|
||||
:global(textarea) {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar) {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar-track) {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar-thumb) {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
:global(textarea::-webkit-scrollbar-thumb:hover) {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
:global(textarea::selection) {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,44 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { chatState, errorStore, streamingStore } from '$lib/store/chat-store';
|
||||
import { afterUpdate } from 'svelte';
|
||||
import { afterUpdate, onMount } from 'svelte';
|
||||
import { toastStore } from '$lib/store/toast-store';
|
||||
import { marked } from 'marked';
|
||||
import SessionManager from './SessionManager.svelte';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
import { ArrowDown } from 'lucide-svelte';
|
||||
import Modal from '$lib/components/ui/modal/Modal.svelte';
|
||||
import PatternList from '$lib/components/patterns/PatternList.svelte';
|
||||
import type { Message } from '$lib/interfaces/chat-interface';
|
||||
import { get } from 'svelte/store';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
|
||||
let messagesContainer: HTMLDivElement;
|
||||
|
||||
afterUpdate(() => {
|
||||
let showPatternModal = false;
|
||||
|
||||
let messagesContainer: HTMLDivElement | null = null;
|
||||
let showScrollButton = false;
|
||||
let isUserMessage = false;
|
||||
|
||||
function scrollToBottom() {
|
||||
if (messagesContainer) {
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
messagesContainer.scrollTo({ top: messagesContainer.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
if (!messagesContainer) return;
|
||||
const { scrollTop, scrollHeight, clientHeight } = messagesContainer;
|
||||
showScrollButton = scrollHeight - scrollTop - clientHeight > 100;
|
||||
}
|
||||
|
||||
// Watch for changes in messages
|
||||
$: if ($chatState.messages.length > 0) {
|
||||
const lastMessage = $chatState.messages[$chatState.messages.length - 1];
|
||||
isUserMessage = lastMessage.role === 'user';
|
||||
if (isUserMessage) {
|
||||
// Only auto-scroll on user messages
|
||||
setTimeout(scrollToBottom, 100);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (messagesContainer) {
|
||||
messagesContainer.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
if (messagesContainer) {
|
||||
messagesContainer.removeEventListener('scroll', handleScroll);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// Configure marked to be synchronous
|
||||
const renderer = new marked.Renderer();
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer,
|
||||
async: false
|
||||
});
|
||||
|
||||
function renderMarkdown(content: string, isAssistant: boolean) {
|
||||
content = content.replace(/\\n/g, '\n');
|
||||
if (!isAssistant) return content;
|
||||
try {
|
||||
return marked.parse(content);
|
||||
} catch (error) {
|
||||
console.error('Error rendering markdown:', error);
|
||||
return content;
|
||||
// New shouldRenderAsMarkdown function
|
||||
function shouldRenderAsMarkdown(message: Message): boolean {
|
||||
const pattern = get(selectedPatternName);
|
||||
if (pattern && message.role === 'assistant') {
|
||||
return message.format !== 'mermaid';
|
||||
}
|
||||
}
|
||||
return message.role === 'assistant' && message.format !== 'plain';
|
||||
}
|
||||
|
||||
// Keep the original renderContent function
|
||||
function renderContent(message: Message): string {
|
||||
const content = message.content.replace(/\\n/g, '\n');
|
||||
|
||||
if (shouldRenderAsMarkdown(message)) {
|
||||
try {
|
||||
return marked.parse(content, { async: false }) as string;
|
||||
} catch (error) {
|
||||
console.error('Error rendering markdown:', error);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="bg-primary-800/30 rounded-lg flex flex-col h-full shadow-lg">
|
||||
<div class="flex justify-between items-center mb-1 mt-1 flex-none">
|
||||
<div class="flex items-center gap-2 pl-4">
|
||||
<b class="text-sm text-muted-foreground font-bold">Chat History</b>
|
||||
<div class="flex justify-between items-center p-3 flex-none border-b border-white/5">
|
||||
<div>
|
||||
<span class="text-xs text-white/70 font-medium">Chat History</span>
|
||||
</div>
|
||||
<SessionManager />
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
show={showPatternModal}
|
||||
on:close={() => showPatternModal = false}
|
||||
>
|
||||
<PatternList on:close={() => showPatternModal = false} />
|
||||
</Modal>
|
||||
|
||||
{#if $errorStore}
|
||||
<div class="error-message" transition:slide>
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
|
||||
@@ -47,16 +113,28 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="messages-container p-4 flex-1 overflow-y-auto max-h-dvh" bind:this={messagesContainer}>
|
||||
<div class="messages-content flex flex-col gap-4">
|
||||
<div
|
||||
class="messages-container p-3 flex-1 overflow-y-auto max-h-dvh relative"
|
||||
bind:this={messagesContainer}
|
||||
>
|
||||
<div class="messages-content flex flex-col gap-3">
|
||||
{#each $chatState.messages as message}
|
||||
<div
|
||||
class="message-item {message.role === 'assistant' ? 'pl-4 bg-primary/5 rounded-lg p-2' : 'pr-4 ml-auto'}"
|
||||
class="message-item {message.role === 'system' ? 'w-full bg-blue-900/20' : message.role === 'assistant' ? 'bg-primary/5 rounded-lg p-3' : 'ml-auto'}"
|
||||
transition:fade
|
||||
class:loading-message={message.format === 'loading'}
|
||||
>
|
||||
<div class="message-header flex items-center gap-2 mb-1 {message.role === 'assistant' ? '' : 'justify-end'}">
|
||||
<span class="text-xs text-muted-foreground rounded-lg p-1 variant-glass-secondary font-bold uppercase">
|
||||
{message.role === 'assistant' ? 'AI' : 'You'}
|
||||
|
||||
|
||||
<div class="message-header flex items-center gap-2 mb-1 {message.role === 'assistant' || message.role === 'system' ? '' : 'justify-end'}">
|
||||
<span class="text-xs text-muted-foreground rounded-lg p-1 variant-glass-secondary font-bold uppercase">
|
||||
{#if message.role === 'system'}
|
||||
SYSTEM
|
||||
{:else if message.role === 'assistant'}
|
||||
AI
|
||||
{:else}
|
||||
You
|
||||
{/if}
|
||||
</span>
|
||||
{#if message.role === 'assistant' && $streamingStore}
|
||||
<span class="loading-indicator flex gap-1">
|
||||
@@ -67,9 +145,13 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if message.role === 'assistant'}
|
||||
<div class="prose prose-slate dark:prose-invert text-inherit prose-headings:text-inherit prose-pre:bg-primary/10 prose-pre:text-inherit text-sm max-w-none">
|
||||
{@html renderMarkdown(message.content, true)}
|
||||
{#if message.role === 'system'}
|
||||
<div class="text-blue-300 text-sm font-semibold">
|
||||
{message.content}
|
||||
</div>
|
||||
{:else if message.role === 'assistant'}
|
||||
<div class="{shouldRenderAsMarkdown(message) ? 'prose prose-slate dark:prose-invert text-inherit prose-headings:text-inherit prose-pre:bg-primary/10 prose-pre:text-inherit' : 'whitespace-pre-wrap'} text-sm max-w-none">
|
||||
{@html renderContent(message)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="whitespace-pre-wrap text-sm">
|
||||
@@ -79,15 +161,31 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if showScrollButton}
|
||||
<button
|
||||
class="absolute bottom-4 right-4 bg-primary/20 hover:bg-primary/30 rounded-full p-2 transition-opacity"
|
||||
on:click={scrollToBottom}
|
||||
transition:fade
|
||||
>
|
||||
<ArrowDown class="w-4 h-4" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/*.chat-messages-wrapper {*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/* min-height: 0;*/
|
||||
/*}*/
|
||||
|
||||
|
||||
:global(.loading-message) {
|
||||
animation: flash 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
@@ -99,7 +197,7 @@
|
||||
.messages-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
@@ -130,8 +228,8 @@
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
:global(.prose pre) {
|
||||
|
||||
35
web/src/lib/components/chat/DropdownGroup.svelte
Normal file
35
web/src/lib/components/chat/DropdownGroup.svelte
Normal file
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import Patterns from "./Patterns.svelte";
|
||||
import Models from "./Models.svelte";
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
const languages = [
|
||||
{ code: '', name: 'Default Language' },
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'fr', name: 'French' },
|
||||
{ code: 'es', name: 'Spanish' },
|
||||
{ code: 'de', name: 'German' },
|
||||
{ code: 'zh', name: 'Chinese' },
|
||||
{ code: 'ja', name: 'Japanese' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="w-[50%]">
|
||||
<Patterns />
|
||||
</div>
|
||||
<div class="w-[50%]">
|
||||
<Models />
|
||||
</div>
|
||||
<div class="w-[50%]">
|
||||
<Select
|
||||
bind:value={$languageStore}
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
{#each languages as lang}
|
||||
<option value={lang.code}>{lang.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,92 +1,94 @@
|
||||
<script lang="ts">
|
||||
export {};
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Slider } from "$lib/components/ui/slider";
|
||||
import { modelConfig } from "$lib/store/model-store";
|
||||
import Transcripts from "./Transcripts.svelte";
|
||||
import NoteDrawer from '$lib/components/ui/noteDrawer/NoteDrawer.svelte';
|
||||
import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { page } from '$app/stores';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { browser } from '$app/environment';
|
||||
import { clickOutside } from '$lib/actions/clickOutside';
|
||||
import Tooltip from "$lib/components/ui/tooltip/Tooltip.svelte";
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
function openDrawer() {
|
||||
drawerStore.open({});
|
||||
// Load expanded state from localStorage
|
||||
const STORAGE_KEY = 'modelConfigExpanded';
|
||||
let isExpanded = false;
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
isExpanded = stored ? JSON.parse(stored) : false;
|
||||
}
|
||||
|
||||
beforeNavigate(() => {
|
||||
drawerStore.close();
|
||||
});
|
||||
// Save expanded state
|
||||
function toggleExpanded() {
|
||||
isExpanded = !isExpanded;
|
||||
saveState();
|
||||
}
|
||||
|
||||
$: isVisible = $page.url.pathname.startsWith('/chat');
|
||||
function saveState() {
|
||||
if (browser) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(isExpanded));
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickOutside() {
|
||||
if (isExpanded) {
|
||||
isExpanded = false;
|
||||
saveState();
|
||||
}
|
||||
}
|
||||
|
||||
const settings = [
|
||||
{ key: 'maxLength', label: 'Maximum Length', min: 1, max: 4000, step: 1, tooltip: "Maximum number of tokens in the response" },
|
||||
{ key: 'temperature', label: 'Temperature', min: 0, max: 2, step: 0.1, tooltip: "Higher values make output more random, lower values more focused" },
|
||||
{ key: 'top_p', label: 'Top P', min: 0, max: 1, step: 0.01, tooltip: "Controls diversity via nucleus sampling" },
|
||||
{ key: 'frequency', label: 'Frequency Penalty', min: 0, max: 1, step: 0.01, tooltip: "Reduces repetition of the same words" },
|
||||
{ key: 'presence', label: 'Presence Penalty', min: 0, max: 1, step: 0.01, tooltip: "Reduces repetition of similar topics" }
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<div class="p-2">
|
||||
<div class="space-y-1">
|
||||
<Label>Maximum Length ({$modelConfig.maxLength})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.maxLength}
|
||||
min={1}
|
||||
max={4000}
|
||||
step={1}
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full" use:clickOutside={handleClickOutside}>
|
||||
<button
|
||||
class="w-full flex items-center py-2 px-2 hover:text-white/90 transition-colors rounded-t"
|
||||
on:click={toggleExpanded}
|
||||
>
|
||||
<span class="text-sm font-semibold">Model Configuration</span>
|
||||
<span class="transform transition-transform duration-200 opacity-70 ml-1 text-xs" class:rotate-180={isExpanded}>
|
||||
▼
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Temperature ({$modelConfig.temperature.toFixed(1)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.temperature}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Top P ({$modelConfig.top_p.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.top_p}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Frequency Penalty ({$modelConfig.frequency.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.frequency}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label>Presence Penalty ({$modelConfig.presence.toFixed(2)})</Label>
|
||||
<Slider
|
||||
bind:value={$modelConfig.presence}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
</div>
|
||||
<br>
|
||||
<div class="space-y-1">
|
||||
<Transcripts />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{#if isVisible}
|
||||
<div class="flex text-inherit justify-start mt-2">
|
||||
<Button
|
||||
variant="primary"
|
||||
class="btn border variant-filled-primary text-align-center"
|
||||
on:click={openDrawer}
|
||||
>Open Drawer
|
||||
</Button>
|
||||
{#if isExpanded}
|
||||
<div
|
||||
class="pt-2 px-2 space-y-3"
|
||||
transition:slide={{
|
||||
duration: 200,
|
||||
easing: cubicOut,
|
||||
}}
|
||||
>
|
||||
{#each settings as setting}
|
||||
<div class="group">
|
||||
<div class="flex justify-between items-center mb-0.5">
|
||||
<Tooltip text={setting.tooltip} position="right">
|
||||
<Label class="text-[10px] text-white/70 cursor-help group-hover:text-white/90 transition-colors">{setting.label}</Label>
|
||||
</Tooltip>
|
||||
<span class="text-[10px] font-mono text-white/50 group-hover:text-white/70 transition-colors">
|
||||
{typeof $modelConfig[setting.key] === 'number' ? $modelConfig[setting.key].toFixed(2) : $modelConfig[setting.key]}
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
bind:value={$modelConfig[setting.key]}
|
||||
min={setting.min}
|
||||
max={setting.max}
|
||||
step={setting.step}
|
||||
class="h-3 group-hover:opacity-90 transition-opacity"
|
||||
/>
|
||||
</div>
|
||||
<NoteDrawer />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.slider) {
|
||||
height: 0.75rem !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
</script>
|
||||
|
||||
<div class="min-w-0">
|
||||
<Select
|
||||
<Select
|
||||
bind:value={$modelConfig.model}
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
<option value="">Default Model</option>
|
||||
{#each $availableModels as model (model.name)}
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { patterns, patternAPI } from "$lib/store/pattern-store";
|
||||
import { patterns, patternAPI, systemPrompt, selectedPatternName } from "$lib/store/pattern-store";
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
let selectedPreset = "";
|
||||
let selectedPreset = $selectedPatternName || "";
|
||||
|
||||
// Subscribe to selectedPatternName changes
|
||||
selectedPatternName.subscribe(value => {
|
||||
if (value && value !== selectedPreset) {
|
||||
console.log('Pattern selected from modal:', value);
|
||||
selectedPreset = value;
|
||||
}
|
||||
});
|
||||
|
||||
// Watch selectedPreset changes
|
||||
$: if (selectedPreset) {
|
||||
console.log('Pattern selected:', selectedPreset);
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
console.log('Pattern selected from dropdown:', selectedPreset);
|
||||
try {
|
||||
patternAPI.selectPattern(selectedPreset);
|
||||
// Verify the selection
|
||||
const currentSystemPrompt = get(systemPrompt);
|
||||
const currentPattern = get(selectedPatternName);
|
||||
console.log('After dropdown selection - Pattern:', currentPattern);
|
||||
console.log('After dropdown selection - System Prompt length:', currentSystemPrompt?.length);
|
||||
|
||||
if (!currentPattern || !currentSystemPrompt) {
|
||||
console.error('Pattern selection verification failed:');
|
||||
console.error('- Selected Pattern:', currentPattern);
|
||||
console.error('- System Prompt:', currentSystemPrompt);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in pattern selection:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
@@ -16,12 +41,13 @@
|
||||
</script>
|
||||
|
||||
<div class="min-w-0">
|
||||
<Select
|
||||
<Select
|
||||
bind:value={selectedPreset}
|
||||
>
|
||||
class="bg-primary-800/30 border-none hover:bg-primary-800/40 transition-colors"
|
||||
>
|
||||
<option value="">Load a pattern...</option>
|
||||
{#each $patterns as pattern}
|
||||
<option value={pattern.Name}>{pattern.Description}</option>
|
||||
<option value={pattern.Name}>{pattern.Name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Sun, Moon, Menu, X, Github } from 'lucide-svelte';
|
||||
import { Sun, Moon, Menu, X, Github, FileText } from 'lucide-svelte';
|
||||
import { Avatar } from '@skeletonlabs/skeleton';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { theme, cycleTheme, initTheme } from '$lib/store/theme-store';
|
||||
// import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import Modal from '$lib/components/ui/modal/Modal.svelte';
|
||||
import PatternList from '$lib/components/patterns/PatternList.svelte';
|
||||
import HelpModal from '$lib/components/ui/help/HelpModal.svelte';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
|
||||
let isMenuOpen = false;
|
||||
let showPatternModal = false;
|
||||
let showHelpModal = false;
|
||||
|
||||
function goToGithub() {
|
||||
window.open('https://github.com/danielmiessler/fabric', '_blank');
|
||||
@@ -66,6 +71,15 @@
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button name="pattern-description"
|
||||
on:click={() => showPatternModal = true}
|
||||
class="inline-flex h-9 items-center justify-center rounded-full border bg-background px-3 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground gap-2"
|
||||
aria-label="Pattern Description"
|
||||
>
|
||||
<FileText class="h-4 w-4" />
|
||||
<span>Pattern Description</span>
|
||||
</button>
|
||||
|
||||
<button name="github"
|
||||
on:click={goToGithub}
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground"
|
||||
@@ -88,6 +102,15 @@
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</button>
|
||||
|
||||
<button name="help"
|
||||
on:click={() => showHelpModal = true}
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-full border bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground ml-3"
|
||||
aria-label="Help"
|
||||
>
|
||||
<span class="text-xl font-bold text-white/90 hover:text-white">?</span>
|
||||
<span class="sr-only">Help</span>
|
||||
</button>
|
||||
|
||||
<!-- Mobile Menu Button -->
|
||||
<button name="toggle-menu"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-lg border bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground md:hidden"
|
||||
@@ -121,3 +144,25 @@
|
||||
</div>
|
||||
{/if}
|
||||
</header>
|
||||
|
||||
<Modal
|
||||
show={showPatternModal}
|
||||
on:close={() => showPatternModal = false}
|
||||
>
|
||||
<PatternList
|
||||
on:close={() => showPatternModal = false}
|
||||
on:select={(e) => {
|
||||
selectedPatternName.set(e.detail);
|
||||
showPatternModal = false;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
show={showHelpModal}
|
||||
on:close={() => showHelpModal = false}
|
||||
>
|
||||
<HelpModal
|
||||
on:close={() => showHelpModal = false}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
209
web/src/lib/components/patterns/PatternList.svelte
Normal file
209
web/src/lib/components/patterns/PatternList.svelte
Normal file
@@ -0,0 +1,209 @@
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { favorites } from '$lib/store/favorites-store';
|
||||
import { patterns, patternAPI, systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import TagFilterPanel from './TagFilterPanel.svelte';
|
||||
|
||||
let tagFilterRef: TagFilterPanel;
|
||||
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
close: void;
|
||||
select: string;
|
||||
tagsChanged: string[]; // Add this line
|
||||
}>();
|
||||
|
||||
|
||||
let patternsContainer: HTMLDivElement;
|
||||
let sortBy: 'alphabetical' | 'favorites' = 'alphabetical';
|
||||
let searchText = ""; // For pattern filtering
|
||||
let selectedTags: string[] = [];
|
||||
|
||||
// First filter patterns by both text and tags
|
||||
// First filter patterns by both text and tags
|
||||
$: filteredPatterns = $patterns
|
||||
.filter((p: Pattern) =>
|
||||
p.Name.toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
.filter((p: Pattern) =>
|
||||
selectedTags.length === 0 ||
|
||||
(p.tags && selectedTags.every(tag => p.tags.includes(tag)))
|
||||
);
|
||||
|
||||
// Then sort the filtered patterns
|
||||
$: sortedPatterns = sortBy === 'alphabetical'
|
||||
? [...filteredPatterns].sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name))
|
||||
: [
|
||||
...filteredPatterns.filter((p: Pattern) => $favorites.includes(p.Name)).sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name)),
|
||||
...filteredPatterns.filter((p: Pattern) => !$favorites.includes(p.Name)).sort((a: Pattern, b: Pattern) => a.Name.localeCompare(b.Name))
|
||||
];
|
||||
|
||||
|
||||
function handleTagFilter(event: CustomEvent<string[]>) {
|
||||
selectedTags = event.detail;
|
||||
}
|
||||
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await patternAPI.loadPatterns();
|
||||
} catch (error) {
|
||||
console.error('Error loading patterns:', error);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleFavorite(name: string) {
|
||||
favorites.toggleFavorite(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="bg-primary-800 rounded-lg flex flex-col h-[85vh] w-[600px] shadow-lg relative">
|
||||
|
||||
<div class="flex flex-col border-b border-primary-700/30">
|
||||
<div class="flex justify-between items-center p-4">
|
||||
<b class="text-lg text-muted-foreground font-bold">Pattern Descriptions</b>
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-4 flex items-center justify-between">
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={sortBy}
|
||||
value="alphabetical"
|
||||
class="radio"
|
||||
>
|
||||
Alphabetical
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input
|
||||
type="radio"
|
||||
bind:group={sortBy}
|
||||
value="favorites"
|
||||
class="radio"
|
||||
>
|
||||
Favorites First
|
||||
</label>
|
||||
</div>
|
||||
<div class="w-64 mr-4">
|
||||
<Input
|
||||
bind:value={searchText}
|
||||
placeholder="Search patterns..."
|
||||
class="text-emerald-900"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New tag display section -->
|
||||
<div class="px-4 pb-2">
|
||||
<div class="text-sm text-white/70 bg-primary-700/30 rounded-md p-2 flex justify-between items-center">
|
||||
<div>Tags: {selectedTags.length ? selectedTags.join(', ') : 'none'}</div>
|
||||
<button
|
||||
class="px-2 py-1 text-xs text-white/70 bg-primary-600/30 rounded hover:bg-primary-600/50 transition-colors"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
dispatch('tagsChanged', selectedTags);
|
||||
}}
|
||||
>
|
||||
reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<TagFilterPanel
|
||||
patterns={$patterns}
|
||||
on:tagsChanged={handleTagFilter}
|
||||
bind:this={tagFilterRef}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="patterns-container p-4 flex-1 overflow-y-auto"
|
||||
bind:this={patternsContainer}
|
||||
>
|
||||
<div class="patterns-list space-y-2">
|
||||
{#each sortedPatterns as pattern}
|
||||
<div class="pattern-item bg-primary/10 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start gap-4 mb-2">
|
||||
<button
|
||||
class="text-xl font-bold text-primary-300 hover:text-primary-100 cursor-pointer transition-colors text-left w-full"
|
||||
on:click={() => {
|
||||
console.log('Selecting pattern:', pattern.Name);
|
||||
patternAPI.selectPattern(pattern.Name);
|
||||
searchText = "";
|
||||
tagFilterRef.reset();
|
||||
dispatch('select', pattern.Name);
|
||||
dispatch('close');
|
||||
}}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
e.currentTarget.click();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{pattern.Name}
|
||||
</button>
|
||||
<button
|
||||
class="text-muted-foreground hover:text-primary-300 transition-colors"
|
||||
on:click={() => toggleFavorite(pattern.Name)}
|
||||
>
|
||||
{#if $favorites.includes(pattern.Name)}
|
||||
★
|
||||
{:else}
|
||||
☆
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground break-words leading-relaxed">{pattern.Description}</p>
|
||||
</div>
|
||||
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.patterns-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: thin;
|
||||
}
|
||||
|
||||
.patterns-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.pattern-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
194
web/src/lib/components/patterns/TagFilterPanel.svelte
Normal file
194
web/src/lib/components/patterns/TagFilterPanel.svelte
Normal file
@@ -0,0 +1,194 @@
|
||||
<script lang="ts">
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
tagsChanged: string[];
|
||||
}>();
|
||||
|
||||
export let patterns: Pattern[];
|
||||
let selectedTags: string[] = [];
|
||||
let isExpanded = false;
|
||||
|
||||
// Add console log to see what tags we're getting
|
||||
$: console.log('Available tags:', Array.from(new Set(patterns.flatMap(p => p.tags))));
|
||||
|
||||
// Add these debug logs
|
||||
$: console.log('Patterns received:', patterns);
|
||||
$: console.log('Tags extracted:', patterns.map(p => p.tags));
|
||||
$: console.log('Panel expanded:', isExpanded);
|
||||
|
||||
function toggleTag(tag: string) {
|
||||
selectedTags = selectedTags.includes(tag)
|
||||
? selectedTags.filter(t => t !== tag)
|
||||
: [...selectedTags, tag];
|
||||
dispatch('tagsChanged', selectedTags);
|
||||
}
|
||||
|
||||
function togglePanel() {
|
||||
isExpanded = !isExpanded;
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
selectedTags = [];
|
||||
isExpanded = false;
|
||||
dispatch('tagsChanged', selectedTags);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tag-panel {isExpanded ? 'expanded' : ''}" style="z-index: 50">
|
||||
<div class="panel-header">
|
||||
<button class="close-btn" on:click={togglePanel}>
|
||||
{isExpanded ? 'Close Filter Tags ◀' : 'Open Filter Tags ▶'}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-content">
|
||||
<div class="reset-container">
|
||||
<button
|
||||
class="reset-btn"
|
||||
on:click={() => {
|
||||
selectedTags = [];
|
||||
dispatch('tagsChanged', selectedTags);
|
||||
}}
|
||||
>
|
||||
Reset All Tags
|
||||
</button>
|
||||
</div>
|
||||
{#each Array.from(new Set(patterns.flatMap(p => p.tags))).sort() as tag}
|
||||
<button
|
||||
class="tag-brick {selectedTags.includes(tag) ? 'selected' : ''}"
|
||||
on:click={() => toggleTag(tag)}
|
||||
>
|
||||
{tag}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.tag-panel {
|
||||
position: fixed; /* Change to fixed positioning */
|
||||
left: calc(50% + 300px); /* Position starts after modal's right edge */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 300px;
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.tag-panel.expanded {
|
||||
left: calc(50% + 360px); /* Final position just to the right of modal */
|
||||
}
|
||||
|
||||
|
||||
.panel-content {
|
||||
display: none;
|
||||
padding: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.tag-brick {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 12px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.reset-container {
|
||||
width: 100%;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--primary-300);
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.expanded .panel-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
/* .toggle-btn {
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 8px;
|
||||
background: var(--primary-800);
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.2);
|
||||
} */
|
||||
|
||||
|
||||
.panel-header {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: auto;
|
||||
padding: 6px;
|
||||
position: absolute;
|
||||
font-size: 0.8rem;
|
||||
color: var(--primary-300);
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Position for 'Open Filter Tags' */
|
||||
.tag-panel:not(.expanded) .close-btn {
|
||||
top: -290px; /* Moves up to search bar level */
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* Position for 'Close Filter Tags' */
|
||||
.expanded .close-btn {
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin-left: -50px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.close-btn:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tag-brick.selected {
|
||||
background: var(--primary-300);
|
||||
}
|
||||
</style>
|
||||
|
||||
27
web/src/lib/components/settings/LanguageSelector.svelte
Normal file
27
web/src/lib/components/settings/LanguageSelector.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Select } from "$lib/components/ui/select";
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
let selectedLanguage = $languageStore;
|
||||
$: languageStore.set(selectedLanguage);
|
||||
|
||||
const languages = [
|
||||
{ code: '', name: 'Default' },
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'fr', name: 'French' },
|
||||
{ code: 'es', name: 'Spanish' },
|
||||
{ code: 'de', name: 'German' },
|
||||
{ code: 'zh', name: 'Chinese' },
|
||||
{ code: 'ja', name: 'Japanese' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<Label>Language</Label>
|
||||
<Select bind:value={selectedLanguage}>
|
||||
{#each languages as lang}
|
||||
<option value={lang.code}>{lang.name}</option>
|
||||
{/each}
|
||||
</Select>
|
||||
</div>
|
||||
@@ -2,9 +2,9 @@
|
||||
import { cn } from "$lib/utils/utils";
|
||||
import { buttonVariants } from "./index.js";
|
||||
|
||||
let className = undefined;
|
||||
export let variant = "";
|
||||
export let size = "default";
|
||||
let className: string | undefined = undefined;
|
||||
export let variant: string = "";
|
||||
export let size: string = "default";
|
||||
export { className as class };
|
||||
|
||||
$: classes = cn(
|
||||
|
||||
22
web/src/lib/components/ui/checkbox/Checkbox.svelte
Normal file
22
web/src/lib/components/ui/checkbox/Checkbox.svelte
Normal file
@@ -0,0 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils/utils";
|
||||
|
||||
export let checked: boolean = false;
|
||||
export let id: string | undefined = undefined;
|
||||
export let disabled: boolean = false;
|
||||
let className: string | undefined = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
{id}
|
||||
bind:checked
|
||||
{disabled}
|
||||
class={cn(
|
||||
"h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-500 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
4
web/src/lib/components/ui/checkbox/index.ts
Normal file
4
web/src/lib/components/ui/checkbox/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import Checkbox from './Checkbox.svelte';
|
||||
|
||||
export { Checkbox };
|
||||
export default Checkbox;
|
||||
164
web/src/lib/components/ui/help/HelpModal.svelte
Normal file
164
web/src/lib/components/ui/help/HelpModal.svelte
Normal file
@@ -0,0 +1,164 @@
|
||||
<script lang="ts">
|
||||
import { X } from 'lucide-svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
close: void;
|
||||
}>();
|
||||
|
||||
let modalContent: HTMLDivElement;
|
||||
|
||||
function handleMouseLeave() {
|
||||
dispatch('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div class="bg-primary-800 rounded-lg flex flex-col h-[85vh] w-[600px] shadow-lg">
|
||||
<div class="flex justify-between items-center p-4 border-b border-primary-700/30">
|
||||
<h2 class="text-xl font-bold text-primary-300">Help</h2>
|
||||
<button class="text-muted-foreground hover:text-primary-300 transition-colors" on:click={() => dispatch('close')}>✕</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 flex-1 overflow-y-auto" bind:this={modalContent}>
|
||||
<div class="space-y-4">
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">Getting Started</h3>
|
||||
<p class="text-sm text-muted-foreground">Click on "Pattern Description" at the top to select a pattern or select a pattern directly from the dropdown menu.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">YouTube & Other website URL</h3>
|
||||
<p class="text-sm text-muted-foreground">Paste a YouTube URL or an article URL in the message box and the link will be processed automatically. Youtube transcripts will be fetch and website html will be converted to markdown for LLM processing. Make sure to setup your Jina API key.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">Language Support</h3>
|
||||
<p class="text-sm text-muted-foreground">Select your preferred language from the dropdown menu. The AI will respond in the selected language.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">Model Configuration</h3>
|
||||
<p class="text-sm text-muted-foreground">Adjust model parameters like temperature and max length to control the AI's output. Higher temperature means more creative but potentially less focused responses.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">I only want a transcript or quick summary</h3>
|
||||
<p class="text-sm text-muted-foreground">Then don't select a pattern and simply ask in the message box "Give me a transcript" and paste the URL. it's that simple.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">File Support</h3>
|
||||
<p class="text-sm text-muted-foreground">You can attach files to your messages using the paperclip icon. The AI will analyze the content of text files.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="text-base font-bold text-primary-300 mb-2">Taking notes and saving to Obsidian</h3>
|
||||
<p class="text-sm text-muted-foreground">Make sure to setup your files path for those options before using.</p>
|
||||
</section>
|
||||
|
||||
<section class="mt-8 pt-4 border-t border-primary-700/30">
|
||||
<h1 class="text-lg font-bold text-primary-300 mb-4">PATTERN TAGS</h1>
|
||||
<p class="text-sm text-muted-foreground mb-6">You can configure the TAGs as you wish and modify or replace these TAGs with yours.</p>
|
||||
<div class="text-sm text-muted-foreground space-y-4">
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">SECURITY</p>
|
||||
<p>Any pattern pertaining to IT Security</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">WRITING</p>
|
||||
<p>Any pattern pertaining to writing, editing, documentation, communication</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">ANALYSIS</p>
|
||||
<p>For patterns like analyze_paper, analyze_claims, analyze_debate</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">VISUALIZATION</p>
|
||||
<p>Any pattern involving some visual representation of something: For example, create logo, presentation diagrams etc.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">DEVELOPMENT</p>
|
||||
<p>Any pattern related to software development. For example, patterns like create_coding_project, create_prd, write_pull-request. Covers software development, coding, technical documentation</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">SUMMARIZATION</p>
|
||||
<p>Covers content condensing and key point extraction. For patterns like create_5_sentence_summary, summarize_meeting, create_micro_summary</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">EXTRACTION</p>
|
||||
<p>For patterns where key focus is mining specific information from content. For example, extract_wisdom, extract_skills, extract_patterns.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">STRATEGY</p>
|
||||
<p>For patterns like analyze_military_strategy, prepare_7s_strategy. Covers planning, decision-making frameworks, mostly with business focus.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">LEARNING</p>
|
||||
<p>Covers educational content and teaching. For patterns like to_flashcards, create_quiz, explain_math</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">CONVERSION</p>
|
||||
<p>Covers format and language transformation. For patterns like convert_to_markdown, translate, sanitize_broken_html_to_markdown</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">REVIEW</p>
|
||||
<p>Covers evaluation and assessment of source. For patterns like review_design, rate_content, analyze_presentation</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">BUSINESS</p>
|
||||
<p>Pattern aiming to support a business or entrepreneurial aim. For patterns like create_hormozi_offer, extract_business_ideas</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">AI</p>
|
||||
<p>For patterns like improve_prompt, rate_ai_response. Covers AI-specific interactions</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">GAMING</p>
|
||||
<p>For patterns like create_npc, create_rpg_summary. Covers gaming and simulation content</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">RESEARCH</p>
|
||||
<p>For patterns like analyze_paper, create_academic_paper. Covers academic and scientific content</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">CRITICAL THINKING</p>
|
||||
<p>Any pattern aiming to improve someone's thinking or aiming to assess evidence.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">SELF</p>
|
||||
<p>Any pattern dealing with personal growth or clearly focus on personal outcome.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">WISDOM</p>
|
||||
<p>Anything worthy of adding to your personal playbook.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-base font-bold text-primary-300 mb-2">OTHER</p>
|
||||
<p>Does not fit anywhere</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import Root from "./input.svelte";
|
||||
export {
|
||||
Root,
|
||||
//
|
||||
Root as Input,
|
||||
};
|
||||
import Input from './Input.svelte';
|
||||
|
||||
export { Input };
|
||||
export default Input;
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils/utils";
|
||||
|
||||
export let value: string = '';
|
||||
export let placeholder: string | undefined = undefined;
|
||||
export let id: string | undefined = undefined;
|
||||
export let disabled: boolean = false;
|
||||
export let required: boolean = false;
|
||||
let className: string | undefined = undefined;
|
||||
export let value: string | undefined = undefined;
|
||||
export { className as class };
|
||||
export let readonly = undefined;
|
||||
</script>
|
||||
|
||||
<input
|
||||
class={cn(
|
||||
"border-input placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-sm shadow-lg transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
{readonly}
|
||||
on:blur
|
||||
on:change
|
||||
on:click
|
||||
on:focus
|
||||
on:focusin
|
||||
on:focusout
|
||||
on:keydown
|
||||
on:keypress
|
||||
on:keyup
|
||||
on:mouseover
|
||||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:mousemove
|
||||
on:paste
|
||||
on:input
|
||||
on:wheel|passive
|
||||
{...$$restProps}
|
||||
type="text"
|
||||
{id}
|
||||
bind:value
|
||||
{placeholder}
|
||||
{disabled}
|
||||
{required}
|
||||
class={cn(
|
||||
"block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils/utils";
|
||||
let className = undefined;
|
||||
let className: string = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<label
|
||||
class={cn(
|
||||
"p-1 text-sm font-bold leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
class="block text-sm font-medium text-gray-700 dark:text-gray-200 {className}"
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</label>
|
||||
<slot />
|
||||
</label>
|
||||
38
web/src/lib/components/ui/modal/Modal.svelte
Normal file
38
web/src/lib/components/ui/modal/Modal.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { fade, scale } from 'svelte/transition';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
close: void;
|
||||
}>();
|
||||
|
||||
export let show = false;
|
||||
</script>
|
||||
|
||||
{#if show}
|
||||
<div
|
||||
class="fixed inset-0 bg-black/50 flex items-center justify-center z-50 mt-2"
|
||||
on:click={() => dispatch('close')}
|
||||
transition:fade={{ duration: 200 }}
|
||||
>
|
||||
<div
|
||||
class="relative"
|
||||
on:click|stopPropagation
|
||||
transition:scale={{ duration: 200 }}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -3,7 +3,8 @@
|
||||
import type { DrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { onMount } from 'svelte';
|
||||
import { noteStore } from '$lib/store/note-store';
|
||||
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
||||
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
||||
import { clickOutside } from '$lib/actions/clickOutside';
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
const toastStore = getToastStore();
|
||||
@@ -82,54 +83,70 @@
|
||||
|
||||
<Drawer width="w-[40%]" class="flex flex-col h-[calc(100vh-theme(spacing.32))] p-4 mt-16">
|
||||
{#if $drawerStore.open}
|
||||
<div class="flex flex-col h-full">
|
||||
<header class="flex-none flex justify-between items-center">
|
||||
<h2 class="m-2 p-1 h2">Notes</h2>
|
||||
<p class="p-2 opacity-70">Notes are saved to <code>`src/lib/content/inbox`</code></p>
|
||||
<p class="p-2 opacity-70">Ctrl + S to save</p>
|
||||
{#if $noteStore.lastSaved}
|
||||
<span class="text-sm opacity-70">
|
||||
Last saved: {$noteStore.lastSaved.toLocaleTimeString()}
|
||||
</span>
|
||||
{/if}
|
||||
<div
|
||||
class="flex flex-col h-full"
|
||||
use:clickOutside={() => {
|
||||
if ($noteStore.isDirty) {
|
||||
if (confirm('You have unsaved changes. Are you sure you want to close?')) {
|
||||
noteStore.reset();
|
||||
drawerStore.close();
|
||||
}
|
||||
} else {
|
||||
drawerStore.close();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<header class="flex-none p-2 border-b border-white/10">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold">Notes</h2>
|
||||
{#if $noteStore.lastSaved}
|
||||
<span class="text-xs opacity-70">
|
||||
Last saved: {$noteStore.lastSaved.toLocaleTimeString()}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex gap-4 mt-2 text-xs opacity-70">
|
||||
<span>Notes saved to <code>inbox/</code></span>
|
||||
<span>Ctrl + S to save</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="p-1">
|
||||
<div class="flex-1 p-4 justify-center items-center m-4">
|
||||
|
||||
<div class="flex-1 p-2">
|
||||
<textarea
|
||||
bind:this={textareaEl}
|
||||
bind:value={$noteStore.content}
|
||||
on:input={adjustTextareaHeight}
|
||||
on:keydown={handleKeydown}
|
||||
class="w-full min-h-96 max-h-[500px] overflow-y-auto resize-none p-2 rounded-container-token text-primary-800"
|
||||
placeholder="Enter your text here..."
|
||||
bind:this={textareaEl}
|
||||
value={$noteStore.content}
|
||||
on:input={e => noteStore.updateContent(e.currentTarget.value)}
|
||||
on:keydown={handleKeydown}
|
||||
class="w-full h-full min-h-[300px] resize-none p-2 rounded-lg bg-primary-800/30 border-none text-sm"
|
||||
placeholder="Enter your text here..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="flex-none flex justify-between items-center p-4 mt-auto">
|
||||
<span class="text-sm opacity-70">
|
||||
{#if $noteStore.isDirty}
|
||||
Unsaved changes
|
||||
|
||||
<footer class="flex-none flex justify-between items-center p-2 border-t border-white/10">
|
||||
<span class="text-xs opacity-70">
|
||||
{#if $noteStore.isDirty}
|
||||
Unsaved changes
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="btn btn-sm variant-filled-surface"
|
||||
on:click={noteStore.reset}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm variant-filled-primary"
|
||||
on:click={saveContent}
|
||||
>
|
||||
{#if saving}
|
||||
Saving...
|
||||
{:else}
|
||||
Save
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex gap-2 m-5">
|
||||
<button
|
||||
class="btn p-2 variant-filled-primary"
|
||||
on:click={noteStore.reset}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
class="btn p-2 variant-filled-primary"
|
||||
on:click={saveContent}
|
||||
>
|
||||
{#if saving}
|
||||
Saving...
|
||||
{:else}
|
||||
Save
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
{/if}
|
||||
</Drawer>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
export let value: any = undefined;
|
||||
export let disabled = false;
|
||||
let className = undefined;
|
||||
let className: string | undefined = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{disabled}
|
||||
bind:value
|
||||
class={cn(
|
||||
"select flex h-8 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-xs shadow-lg ring-offset-background placeholder:text-muted-primary focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 appearance-none",
|
||||
"select flex h-8 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-xs font-medium text-white/90 shadow-lg ring-offset-background placeholder:text-muted-primary focus:outline-none focus:ring-1 focus:ring-ring hover:text-white disabled:cursor-not-allowed disabled:opacity-50 appearance-none transition-colors",
|
||||
className
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils/utils.ts";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils/utils";
|
||||
|
||||
let className = undefined;
|
||||
let className: string | undefined = undefined;
|
||||
export let value = 0;
|
||||
export let min = 0;
|
||||
export let max = 100;
|
||||
export let step = 1;
|
||||
export { className as class };
|
||||
|
||||
let sliderEl;
|
||||
let sliderEl: HTMLDivElement;
|
||||
let isDragging = false;
|
||||
|
||||
$: percentage = ((value - min) / (max - min)) * 100;
|
||||
|
||||
function handleMouseDown(e) {
|
||||
function handleMouseDown(e: MouseEvent) {
|
||||
isDragging = true;
|
||||
updateValue(e);
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
|
||||
function handleMouseMove(e) {
|
||||
function handleMouseMove(e: MouseEvent) {
|
||||
if (!isDragging) return;
|
||||
updateValue(e);
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
window.removeEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
|
||||
function updateValue(e) {
|
||||
function updateValue(e: MouseEvent) {
|
||||
if (!sliderEl) return;
|
||||
const rect = sliderEl.getBoundingClientRect();
|
||||
const pos = (e.clientX - rect.left) / rect.width;
|
||||
@@ -40,8 +40,8 @@
|
||||
value = Math.max(min, Math.min(max, steppedValue));
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
const step = e.shiftKey ? 10 : 1;
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
const stepSize = e.shiftKey ? 10 : 1;
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowDown':
|
||||
@@ -76,14 +76,14 @@
|
||||
aria-valuemax={max}
|
||||
aria-valuenow={value}
|
||||
>
|
||||
<div class="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
|
||||
<div class="relative h-0.5 w-full grow overflow-hidden rounded-full bg-white/10">
|
||||
<div
|
||||
class="absolute h-full bg-primary transition-all"
|
||||
class="absolute h-full bg-white/30 transition-all"
|
||||
style="width: {percentage}%"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="absolute h-4 w-4 rounded-full border border-secondary bg-primary-500 shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
|
||||
style="left: calc({percentage}% - 0.5rem)"
|
||||
class="absolute h-2.5 w-2.5 rounded-full bg-white/70 ring-1 ring-white/10 shadow-sm transition-all hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30 disabled:pointer-events-none disabled:opacity-50"
|
||||
style="left: calc({percentage}% - 0.3125rem)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type { ToastMessage } from '$lib/store/toast-store';
|
||||
|
||||
export let toast: ToastMessage;
|
||||
const TOAST_TIMEOUT = 3000;
|
||||
const TOAST_TIMEOUT = 5000;
|
||||
|
||||
onMount(() => {
|
||||
const timer = setTimeout(() => {
|
||||
|
||||
114
web/src/lib/components/ui/tooltip/Tooltip.svelte
Normal file
114
web/src/lib/components/ui/tooltip/Tooltip.svelte
Normal file
@@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
export let text: string;
|
||||
export let position: 'top' | 'bottom' | 'left' | 'right' = 'top';
|
||||
|
||||
let tooltipVisible = false;
|
||||
let tooltipElement: HTMLDivElement;
|
||||
|
||||
function showTooltip() {
|
||||
tooltipVisible = true;
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
tooltipVisible = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tooltip-container">
|
||||
<div
|
||||
class="tooltip-trigger"
|
||||
on:mouseenter={showTooltip}
|
||||
on:mouseleave={hideTooltip}
|
||||
on:focusin={showTooltip}
|
||||
on:focusout={hideTooltip}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{#if tooltipVisible}
|
||||
<div
|
||||
bind:this={tooltipElement}
|
||||
class="tooltip absolute z-[9999] px-2 py-1 text-xs rounded bg-gray-900/90 text-white whitespace-nowrap shadow-lg backdrop-blur-sm"
|
||||
class:top="{position === 'top'}"
|
||||
class:bottom="{position === 'bottom'}"
|
||||
class:left="{position === 'left'}"
|
||||
class:right="{position === 'right'}"
|
||||
>
|
||||
{text}
|
||||
<div class="tooltip-arrow" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tooltip-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-trigger {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
pointer-events: none;
|
||||
transition: all 150ms ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltip.top {
|
||||
bottom: calc(100% + 5px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.bottom {
|
||||
top: calc(100% + 5px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.left {
|
||||
right: calc(100% + 5px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.tooltip.right {
|
||||
left: calc(100% + 5px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: inherit;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.tooltip.top .tooltip-arrow {
|
||||
bottom: -4px;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.tooltip.bottom .tooltip-arrow {
|
||||
top: -4px;
|
||||
left: 50%;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.tooltip.left .tooltip-arrow {
|
||||
right: -4px;
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.tooltip.right .tooltip-arrow {
|
||||
left: -4px;
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
}
|
||||
</style>
|
||||
16
web/src/lib/config/features.ts
Normal file
16
web/src/lib/config/features.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
interface FeatureFlags {
|
||||
enableObsidianIntegration: boolean;
|
||||
}
|
||||
|
||||
export const featureFlags = writable<FeatureFlags>({
|
||||
enableObsidianIntegration: true // Set to true for development
|
||||
});
|
||||
|
||||
export function toggleObsidianIntegration(enabled: boolean) {
|
||||
featureFlags.update(flags => ({
|
||||
...flags,
|
||||
enableObsidianIntegration: enabled
|
||||
}));
|
||||
}
|
||||
16
web/src/lib/interfaces/LanguageDisplay.svelte
Normal file
16
web/src/lib/interfaces/LanguageDisplay.svelte
Normal file
@@ -0,0 +1,16 @@
|
||||
<script>
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
</script>
|
||||
|
||||
<div class="language-indicator">
|
||||
Current Language: {$languageStore}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.language-indicator {
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
export type MessageRole = 'system' | 'user' | 'assistant';
|
||||
export type ResponseFormat = 'markdown' | 'mermaid' | 'plain';
|
||||
export type ResponseFormat = 'markdown' | 'mermaid' | 'plain' | 'loading';
|
||||
export type ResponseType = 'content' | 'error' | 'complete';
|
||||
|
||||
export interface ChatPrompt {
|
||||
@@ -28,6 +28,7 @@ export interface ChatRequest {
|
||||
export interface Message {
|
||||
role: MessageRole;
|
||||
content: string;
|
||||
format?: ResponseFormat;
|
||||
}
|
||||
|
||||
export interface ChatState {
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
export interface Pattern {
|
||||
Name: string;
|
||||
Description: string;
|
||||
Pattern: string; // | object
|
||||
import type { StorageEntity } from './storage-interface';
|
||||
|
||||
// Interface matching the JSON structure from pattern_descriptions.json
|
||||
export interface PatternDescription {
|
||||
patternName: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// Interface for storage compatibility - must use uppercase for StorageEntity
|
||||
export interface Pattern extends StorageEntity {
|
||||
Name: string; // maps to patternName from JSON
|
||||
Description: string; // maps to description from JSON
|
||||
Pattern: string; // pattern content from API
|
||||
tags: string[]; // array of tag strings
|
||||
}
|
||||
@@ -1,168 +1,240 @@
|
||||
import type {
|
||||
ChatRequest,
|
||||
StreamResponse,
|
||||
ChatError as IChatError,
|
||||
ChatPrompt
|
||||
import type {
|
||||
ChatRequest,
|
||||
StreamResponse,
|
||||
ChatError as IChatError,
|
||||
ChatPrompt
|
||||
} from '$lib/interfaces/chat-interface';
|
||||
import { get } from 'svelte/store';
|
||||
import { modelConfig } from '$lib/store/model-store';
|
||||
import { systemPrompt } from '$lib/store/pattern-store';
|
||||
import { systemPrompt, selectedPatternName } from '$lib/store/pattern-store';
|
||||
import { chatConfig } from '$lib/store/chat-config';
|
||||
import { messageStore } from '$lib/store/chat-store'; // Import messageStore
|
||||
import { messageStore } from '$lib/store/chat-store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
class LanguageValidator {
|
||||
constructor(private targetLanguage: string) {}
|
||||
|
||||
enforceLanguage(content: string): string {
|
||||
if (this.targetLanguage === 'en') return content;
|
||||
return `[Language: ${this.targetLanguage}]\n${content}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatError extends Error implements IChatError {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string = 'CHAT_ERROR',
|
||||
public readonly details?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ChatError';
|
||||
}
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string = 'CHAT_ERROR',
|
||||
public readonly details?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ChatError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatService {
|
||||
private async fetchStream(request: ChatRequest): Promise<ReadableStream<StreamResponse>> {
|
||||
try {
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
private validator: LanguageValidator;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ChatError(
|
||||
`HTTP error! status: ${response.status}`,
|
||||
'HTTP_ERROR',
|
||||
{ status: response.status }
|
||||
);
|
||||
}
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new ChatError('Response body is null', 'NULL_RESPONSE');
|
||||
}
|
||||
|
||||
return this.createMessageStream(reader);
|
||||
} catch (error) {
|
||||
if (error instanceof ChatError) {
|
||||
throw error;
|
||||
}
|
||||
throw new ChatError(
|
||||
'Failed to fetch chat stream',
|
||||
'FETCH_ERROR',
|
||||
error
|
||||
);
|
||||
constructor() {
|
||||
this.validator = new LanguageValidator(get(languageStore));
|
||||
}
|
||||
}
|
||||
|
||||
private createMessageStream(reader: ReadableStreamDefaultReader<Uint8Array>): ReadableStream<StreamResponse> {
|
||||
let buffer = '';
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
private async fetchStream(request: ChatRequest): Promise<ReadableStream<StreamResponse>> {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
console.log('\n=== ChatService Request Start ===');
|
||||
console.log('1. Request details:', {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
promptCount: request.prompts?.length,
|
||||
messageCount: request.messages?.length
|
||||
});
|
||||
|
||||
buffer += new TextDecoder().decode(value);
|
||||
const messages = buffer
|
||||
.split('\n\n')
|
||||
.filter(msg => msg.startsWith('data: '));
|
||||
const response = await fetch('/api/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(request),
|
||||
});
|
||||
|
||||
if (messages.length > 1) {
|
||||
buffer = messages.pop() || '';
|
||||
|
||||
for (const msg of messages) {
|
||||
controller.enqueue(JSON.parse(msg.slice(6)) as StreamResponse);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new ChatError(`HTTP error! status: ${response.status}`, 'HTTP_ERROR', { status: response.status });
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.startsWith('data: ')) {
|
||||
controller.enqueue(JSON.parse(buffer.slice(6)) as StreamResponse);
|
||||
}
|
||||
const reader = response.body?.getReader();
|
||||
if (!reader) {
|
||||
throw new ChatError('Response body is null', 'NULL_RESPONSE');
|
||||
}
|
||||
|
||||
return this.createMessageStream(reader);
|
||||
} catch (error) {
|
||||
controller.error(new ChatError(
|
||||
'Error processing stream',
|
||||
'STREAM_PROCESSING_ERROR',
|
||||
error
|
||||
));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
controller.close();
|
||||
if (error instanceof ChatError) throw error;
|
||||
throw new ChatError('Failed to fetch chat stream', 'FETCH_ERROR', error);
|
||||
}
|
||||
},
|
||||
|
||||
cancel() {
|
||||
reader.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createChatPrompt(userInput: string, systemPromptText?: string): ChatPrompt {
|
||||
const config = get(modelConfig);
|
||||
return {
|
||||
userInput,
|
||||
systemPrompt: systemPromptText ?? get(systemPrompt),
|
||||
model: config.model,
|
||||
patternName: ''
|
||||
};
|
||||
}
|
||||
|
||||
public async createChatRequest(userInput: string, systemPromptText?: string): Promise<ChatRequest> {
|
||||
const prompt = this.createChatPrompt(userInput, systemPromptText);
|
||||
const config = get(chatConfig);
|
||||
const messages = get(messageStore);
|
||||
|
||||
return {
|
||||
prompts: [prompt],
|
||||
messages: messages,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
public async streamChat(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async processStream(
|
||||
stream: ReadableStream<StreamResponse>,
|
||||
onContent: (content: string) => void,
|
||||
onError: (error: Error) => void
|
||||
): Promise<void> {
|
||||
const reader = stream.getReader();
|
||||
let accumulatedContent = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) break;
|
||||
|
||||
if (value.type === 'error') {
|
||||
throw new ChatError(value.content, 'STREAM_CONTENT_ERROR');
|
||||
}
|
||||
|
||||
accumulatedContent += value.content;
|
||||
onContent(accumulatedContent);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ChatError) {
|
||||
onError(error);
|
||||
} else {
|
||||
onError(new ChatError(
|
||||
'Error processing stream content',
|
||||
'STREAM_PROCESSING_ERROR',
|
||||
error
|
||||
));
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
private cleanPatternOutput(content: string): string {
|
||||
// Remove markdown fence if present
|
||||
content = content.replace(/^```markdown\n/, '');
|
||||
content = content.replace(/\n```$/, '');
|
||||
|
||||
// Existing cleaning
|
||||
content = content.replace(/^# OUTPUT\s*\n/, '');
|
||||
content = content.replace(/^\s*\n/, '');
|
||||
content = content.replace(/\n\s*$/, '');
|
||||
content = content.replace(/^#\s+([A-Z]+):/gm, '$1:');
|
||||
content = content.replace(/^#\s+([A-Z]+)\s*$/gm, '$1');
|
||||
content = content.trim();
|
||||
content = content.replace(/\n{3,}/g, '\n\n');
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private createMessageStream(reader: ReadableStreamDefaultReader<Uint8Array>): ReadableStream<StreamResponse> {
|
||||
let buffer = '';
|
||||
const cleanPatternOutput = this.cleanPatternOutput.bind(this);
|
||||
const language = get(languageStore);
|
||||
const validator = new LanguageValidator(language);
|
||||
|
||||
const processResponse = (response: StreamResponse) => {
|
||||
const pattern = get(selectedPatternName);
|
||||
|
||||
if (pattern) {
|
||||
response.content = cleanPatternOutput(response.content);
|
||||
// Simplified format determination - always markdown unless mermaid
|
||||
const isMermaid = [
|
||||
'graph TD', 'gantt', 'flowchart',
|
||||
'sequenceDiagram', 'classDiagram', 'stateDiagram'
|
||||
].some(starter => response.content.trim().startsWith(starter));
|
||||
|
||||
response.format = isMermaid ? 'mermaid' : 'markdown';
|
||||
}
|
||||
|
||||
if (response.type === 'content') {
|
||||
response.content = validator.enforceLanguage(response.content);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += new TextDecoder().decode(value);
|
||||
const messages = buffer.split('\n\n').filter(msg => msg.startsWith('data: '));
|
||||
|
||||
if (messages.length > 1) {
|
||||
buffer = messages.pop() || '';
|
||||
for (const msg of messages) {
|
||||
try {
|
||||
let response = JSON.parse(msg.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing stream message:', parseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.startsWith('data: ')) {
|
||||
try {
|
||||
let response = JSON.parse(buffer.slice(6)) as StreamResponse;
|
||||
response = processResponse(response);
|
||||
controller.enqueue(response);
|
||||
} catch (parseError) {
|
||||
console.error('Error parsing final message:', parseError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(new ChatError('Stream processing error', 'STREAM_ERROR', error));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
reader.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createChatPrompt(userInput: string, systemPromptText?: string): ChatPrompt {
|
||||
const config = get(modelConfig);
|
||||
const language = get(languageStore);
|
||||
|
||||
const languageInstruction = language !== 'en'
|
||||
? `You MUST respond in ${language} language. All output including section headers and formatting must be in ${language}. Translate headers like SUMMARY, IDEAS, QUOTES etc into ${language}. Maintain markdown formatting in the response.`
|
||||
: '';
|
||||
|
||||
const finalSystemPrompt = languageInstruction + (systemPromptText ?? get(systemPrompt));
|
||||
|
||||
const finalUserInput = language !== 'en'
|
||||
? `${userInput}\n\nIMPORTANT: Respond in ${language} language only.`
|
||||
: userInput;
|
||||
|
||||
return {
|
||||
userInput: finalUserInput,
|
||||
systemPrompt: finalSystemPrompt,
|
||||
model: config.model,
|
||||
patternName: get(selectedPatternName)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public async createChatRequest(userInput: string, systemPromptText?: string, isPattern: boolean = false): Promise<ChatRequest> {
|
||||
const prompt = this.createChatPrompt(userInput, systemPromptText);
|
||||
const config = get(chatConfig);
|
||||
|
||||
return {
|
||||
prompts: [prompt],
|
||||
messages: [],
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
public async streamPattern(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText, true);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async streamChat(userInput: string, systemPromptText?: string): Promise<ReadableStream<StreamResponse>> {
|
||||
const request = await this.createChatRequest(userInput, systemPromptText);
|
||||
return this.fetchStream(request);
|
||||
}
|
||||
|
||||
public async processStream(
|
||||
stream: ReadableStream<StreamResponse>,
|
||||
onContent: (content: string, response?: StreamResponse) => void,
|
||||
onError: (error: Error) => void
|
||||
): Promise<void> {
|
||||
const reader = stream.getReader();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
if (value.type === 'error') {
|
||||
throw new ChatError(value.content, 'STREAM_CONTENT_ERROR');
|
||||
}
|
||||
|
||||
if (value.type === 'content') {
|
||||
onContent(value.content, value);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
onError(error instanceof ChatError ? error : new ChatError('Stream processing error', 'STREAM_ERROR', error));
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +1,80 @@
|
||||
import { YoutubeTranscript } from 'youtube-transcript';
|
||||
import { get } from 'svelte/store';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
|
||||
export interface TranscriptResponse {
|
||||
transcript: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
function decodeHtmlEntities(text: string): string {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.innerHTML = text;
|
||||
return textarea.value;
|
||||
}
|
||||
|
||||
export async function getTranscript(url: string): Promise<TranscriptResponse> {
|
||||
try {
|
||||
const videoId = extractVideoId(url);
|
||||
if (!videoId) {
|
||||
throw new Error('Invalid YouTube URL');
|
||||
const originalLanguage = get(languageStore);
|
||||
console.log('\n=== YouTube Transcript Service Start ===');
|
||||
console.log('1. Request details:', {
|
||||
url,
|
||||
endpoint: '/chat',
|
||||
method: 'POST',
|
||||
isYouTubeURL: url.includes('youtube.com') || url.includes('youtu.be'),
|
||||
originalLanguage
|
||||
});
|
||||
|
||||
const response = await fetch('/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url,
|
||||
language: originalLanguage // Pass original language to server
|
||||
})
|
||||
});
|
||||
|
||||
console.log('2. Server response:', {
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
type: response.type,
|
||||
originalLanguage,
|
||||
currentLanguage: get(languageStore)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const transcriptItems = await YoutubeTranscript.fetchTranscript(videoId);
|
||||
const transcript = transcriptItems
|
||||
.map(item => item.text)
|
||||
.join(' ');
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
const transcriptTitle = transcriptItems
|
||||
.map(item => item.text)
|
||||
.join('');
|
||||
// Decode HTML entities in transcript
|
||||
data.transcript = decodeHtmlEntities(data.transcript);
|
||||
|
||||
// TODO: Add title fetching
|
||||
return {
|
||||
transcript,
|
||||
title: videoId // Just returning the video ID as title
|
||||
};
|
||||
// Ensure language is preserved
|
||||
if (get(languageStore) !== originalLanguage) {
|
||||
console.log('3a. Restoring original language:', originalLanguage);
|
||||
languageStore.set(originalLanguage);
|
||||
}
|
||||
|
||||
console.log('3b. Processed transcript:', {
|
||||
status: response.status,
|
||||
transcriptLength: data.transcript.length,
|
||||
firstChars: data.transcript.substring(0, 100),
|
||||
hasError: !!data.error,
|
||||
videoId: data.title,
|
||||
originalLanguage,
|
||||
currentLanguage: get(languageStore)
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Transcript fetch error:', error);
|
||||
throw new Error('Failed to fetch transcript');
|
||||
throw error instanceof Error ? error : new Error('Failed to fetch transcript');
|
||||
}
|
||||
}
|
||||
|
||||
function extractVideoId(url: string): string | null {
|
||||
const match = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { writable, derived, get } from 'svelte/store';
|
||||
import type { ChatState, Message } from '$lib/interfaces/chat-interface';
|
||||
import type { ChatState, Message, StreamResponse } from '$lib/interfaces/chat-interface';
|
||||
import { ChatService, ChatError } from '$lib/services/ChatService';
|
||||
import { languageStore } from '$lib/store/language-store';
|
||||
import { selectedPatternName } from '$lib/store/pattern-store';
|
||||
|
||||
// Initialize chat service
|
||||
const chatService = new ChatService();
|
||||
@@ -67,54 +69,86 @@ export const revertLastMessage = () => {
|
||||
messageStore.update(messages => messages.slice(0, -1));
|
||||
};
|
||||
|
||||
export async function sendMessage(userInput: string, systemPromptText?: string) {
|
||||
try {
|
||||
const $streaming = get(streamingStore);
|
||||
if ($streaming) {
|
||||
throw new ChatError('Message submission blocked - already streaming', 'STREAMING_BLOCKED');
|
||||
}
|
||||
|
||||
streamingStore.set(true);
|
||||
errorStore.set(null);
|
||||
|
||||
// Add user message
|
||||
messageStore.update(messages => [...messages, { role: 'user', content: userInput }]);
|
||||
|
||||
const stream = await chatService.streamChat(userInput, systemPromptText);
|
||||
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content = content;
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content
|
||||
});
|
||||
}
|
||||
|
||||
return newMessages;
|
||||
export async function sendMessage(content: string, systemPromptText?: string, isSystem: boolean = false) {
|
||||
try {
|
||||
console.log('\n=== Message Processing Start ===');
|
||||
console.log('1. Initial state:', {
|
||||
isSystem,
|
||||
hasSystemPrompt: !!systemPromptText,
|
||||
currentLanguage: get(languageStore),
|
||||
pattern: get(selectedPatternName)
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
handleError(error);
|
||||
}
|
||||
);
|
||||
|
||||
streamingStore.set(false);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
handleError(String(error));
|
||||
const $streaming = get(streamingStore);
|
||||
if ($streaming) {
|
||||
throw new ChatError('Message submission blocked - already streaming', 'STREAMING_BLOCKED');
|
||||
}
|
||||
|
||||
streamingStore.set(true);
|
||||
errorStore.set(null);
|
||||
|
||||
// Add message
|
||||
messageStore.update(messages => [...messages, {
|
||||
role: isSystem ? 'system' : 'user',
|
||||
content
|
||||
}]);
|
||||
|
||||
console.log('2. Message added:', {
|
||||
role: isSystem ? 'system' : 'user',
|
||||
language: get(languageStore)
|
||||
});
|
||||
|
||||
if (!isSystem) {
|
||||
console.log('3. Preparing chat stream:', {
|
||||
language: get(languageStore),
|
||||
pattern: get(selectedPatternName),
|
||||
hasSystemPrompt: !!systemPromptText
|
||||
});
|
||||
|
||||
const stream = await chatService.streamChat(content, systemPromptText);
|
||||
console.log('4. Stream created');
|
||||
|
||||
await chatService.processStream(
|
||||
stream,
|
||||
(content: string, response?: StreamResponse) => {
|
||||
messageStore.update(messages => {
|
||||
const newMessages = [...messages];
|
||||
const lastMessage = newMessages[newMessages.length - 1];
|
||||
|
||||
if (lastMessage?.role === 'assistant') {
|
||||
lastMessage.content = content;
|
||||
lastMessage.format = response?.format;
|
||||
console.log('Message updated:', {
|
||||
role: 'assistant',
|
||||
format: lastMessage.format
|
||||
});
|
||||
} else {
|
||||
newMessages.push({
|
||||
role: 'assistant',
|
||||
content,
|
||||
format: response?.format
|
||||
});
|
||||
}
|
||||
|
||||
return newMessages;
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
handleError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
streamingStore.set(false);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
handleError(error);
|
||||
} else {
|
||||
handleError(String(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export types for convenience
|
||||
|
||||
36
web/src/lib/store/favorites-store.ts
Normal file
36
web/src/lib/store/favorites-store.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
// Load favorites from localStorage if available
|
||||
const storedFavorites = typeof localStorage !== 'undefined'
|
||||
? JSON.parse(localStorage.getItem('favoritePatterns') || '[]')
|
||||
: [];
|
||||
|
||||
const createFavoritesStore = () => {
|
||||
const { subscribe, set, update } = writable<string[]>(storedFavorites);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
toggleFavorite: (patternName: string) => {
|
||||
update(favorites => {
|
||||
const newFavorites = favorites.includes(patternName)
|
||||
? favorites.filter(name => name !== patternName)
|
||||
: [...favorites, patternName];
|
||||
|
||||
// Save to localStorage
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem('favoritePatterns', JSON.stringify(newFavorites));
|
||||
}
|
||||
|
||||
return newFavorites;
|
||||
});
|
||||
},
|
||||
reset: () => {
|
||||
set([]);
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.removeItem('favoritePatterns');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const favorites = createFavoritesStore();
|
||||
13
web/src/lib/store/language-store.ts
Normal file
13
web/src/lib/store/language-store.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
const storedLanguage = browser ? localStorage.getItem('selectedLanguage') || 'en' : 'en';
|
||||
const languageStore = writable<string>(storedLanguage);
|
||||
|
||||
if (browser) {
|
||||
languageStore.subscribe(value => {
|
||||
localStorage.setItem('selectedLanguage', value);
|
||||
});
|
||||
}
|
||||
|
||||
export { languageStore };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { writable} from 'svelte/store';
|
||||
import { writable } from 'svelte/store';
|
||||
import { modelsApi } from '$lib/api/models';
|
||||
import { configApi } from '$lib/api/config';
|
||||
import type { VendorModel, ModelConfig } from '$lib/interfaces/model-interface';
|
||||
|
||||
@@ -10,105 +10,99 @@ interface NoteState {
|
||||
|
||||
function createNoteStore() {
|
||||
const { subscribe, set, update } = writable<NoteState>({
|
||||
content: '',
|
||||
lastSaved: null,
|
||||
isDirty: false
|
||||
content: '',
|
||||
lastSaved: null,
|
||||
isDirty: false
|
||||
});
|
||||
|
||||
const createFrontmatter = (content: string): Frontmatter => {
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString();
|
||||
const now = new Date();
|
||||
const dateStr = now.toISOString();
|
||||
const title = `Note ${now.toLocaleString()}`;
|
||||
const cleanContent = content
|
||||
.replace(/[#*`_]/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// Generate a timestamp-based title instead of using content
|
||||
const title = `Note ${now.toLocaleString()}`;
|
||||
|
||||
// Clean up content for description - remove markdown and extra whitespace
|
||||
const cleanContent = content
|
||||
.replace(/[#*`_]/g, '') // Remove markdown characters
|
||||
.replace(/\s+/g, ' ') // Normalize whitespace
|
||||
.trim();
|
||||
|
||||
return {
|
||||
title,
|
||||
aliases: [''],
|
||||
description: cleanContent.slice(0, 150) + (cleanContent.length > 150 ? '...' : ''),
|
||||
date: dateStr,
|
||||
tags: ['inbox', 'note'],
|
||||
updated: dateStr,
|
||||
author: 'User',
|
||||
};
|
||||
return {
|
||||
title,
|
||||
aliases: [''],
|
||||
description: cleanContent.slice(0, 150) + (cleanContent.length > 150 ? '...' : ''),
|
||||
date: dateStr,
|
||||
tags: ['inbox', 'note'],
|
||||
updated: dateStr,
|
||||
author: 'User',
|
||||
};
|
||||
};
|
||||
|
||||
const generateUniqueFilename = () => {
|
||||
const now = new Date();
|
||||
const date = now.toISOString().split('T')[0];
|
||||
const time = now.toISOString().split('T')[1]
|
||||
.replace(/:/g, '-')
|
||||
.split('.')[0];
|
||||
return `${date}-${time}.md`;
|
||||
const now = new Date();
|
||||
const date = now.toISOString().split('T')[0];
|
||||
const time = now.toISOString().split('T')[1]
|
||||
.replace(/:/g, '-')
|
||||
.split('.')[0];
|
||||
return `${date}-${time}.md`;
|
||||
};
|
||||
|
||||
const saveToFile = async (content: string) => {
|
||||
if (!browser) return;
|
||||
if (!browser) return;
|
||||
|
||||
const filename = generateUniqueFilename();
|
||||
const frontmatter = createFrontmatter(content);
|
||||
|
||||
// Format frontmatter without extra indentation
|
||||
const fileContent = `---
|
||||
const filename = generateUniqueFilename();
|
||||
const frontmatter = createFrontmatter(content);
|
||||
const fileContent = `---
|
||||
title: ${frontmatter.title}
|
||||
aliases: [${frontmatter.aliases.map(aliases => `"${aliases}"`).join(', ')}]
|
||||
aliases: [${(frontmatter.aliases || []).map(alias => `"${alias}"`).join(', ')}]
|
||||
description: ${frontmatter.description}
|
||||
date: ${frontmatter.date}
|
||||
tags: [${frontmatter.tags.map(tag => `"${tag}"`).join(', ')}]
|
||||
tags: [${(frontmatter.tags || []).map(tag => `"${tag}"`).join(', ')}]
|
||||
updated: ${frontmatter.updated}
|
||||
author: ${frontmatter.author}
|
||||
---
|
||||
|
||||
${content}`; // Original content preserved as-is
|
||||
${content}`;
|
||||
|
||||
const response = await fetch('/notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filename,
|
||||
content: fileContent
|
||||
})
|
||||
});
|
||||
const response = await fetch('/notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filename,
|
||||
content: fileContent
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
return filename;
|
||||
return filename;
|
||||
};
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
updateContent: (content: string) => update(state => ({
|
||||
...state,
|
||||
content,
|
||||
isDirty: true
|
||||
})),
|
||||
save: async () => {
|
||||
const state = get({ subscribe });
|
||||
const filename = await saveToFile(state.content);
|
||||
subscribe,
|
||||
updateContent: (content: string) => update(state => ({
|
||||
...state,
|
||||
content,
|
||||
isDirty: true
|
||||
})),
|
||||
save: async () => {
|
||||
const state = get({ subscribe });
|
||||
const filename = await saveToFile(state.content);
|
||||
|
||||
update(state => ({
|
||||
...state,
|
||||
lastSaved: new Date(),
|
||||
isDirty: false
|
||||
}));
|
||||
update(state => ({
|
||||
...state,
|
||||
lastSaved: new Date(),
|
||||
isDirty: false
|
||||
}));
|
||||
|
||||
return filename;
|
||||
},
|
||||
reset: () => set({
|
||||
content: '',
|
||||
lastSaved: null,
|
||||
isDirty: false
|
||||
})
|
||||
return filename;
|
||||
},
|
||||
reset: () => set({
|
||||
content: '',
|
||||
lastSaved: null,
|
||||
isDirty: false
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
68
web/src/lib/store/obsidian-store.ts
Normal file
68
web/src/lib/store/obsidian-store.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { featureFlags } from '../config/features';
|
||||
|
||||
export interface ObsidianSettings {
|
||||
saveToObsidian: boolean;
|
||||
noteName: string;
|
||||
}
|
||||
|
||||
// Keep existing defaultSettings
|
||||
const defaultSettings: ObsidianSettings = {
|
||||
saveToObsidian: false,
|
||||
noteName: ''
|
||||
};
|
||||
|
||||
// Keep existing store initialization
|
||||
export const obsidianSettings = writable<ObsidianSettings>(defaultSettings);
|
||||
|
||||
// Add notification store
|
||||
export const saveNotification = writable<string>('');
|
||||
|
||||
// Keep existing update function with notification enhancement
|
||||
export function updateObsidianSettings(settings: Partial<ObsidianSettings>) {
|
||||
const enabled = get(featureFlags).enableObsidianIntegration;
|
||||
console.log('Updating Obsidian settings:', settings, 'Integration enabled:', enabled);
|
||||
|
||||
if (!enabled) {
|
||||
console.log('Obsidian integration disabled, not updating settings');
|
||||
return;
|
||||
}
|
||||
|
||||
obsidianSettings.update(current => {
|
||||
const updated = {
|
||||
...current,
|
||||
...settings
|
||||
};
|
||||
|
||||
// Add notification after successful save
|
||||
if (settings.saveToObsidian === false && current.noteName) {
|
||||
saveNotification.set('Note saved to Obsidian!');
|
||||
setTimeout(() => saveNotification.set(''), 3000);
|
||||
}
|
||||
|
||||
console.log('Updated Obsidian settings:', updated);
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
|
||||
// Reset settings to default
|
||||
export function resetObsidianSettings() {
|
||||
const enabled = get(featureFlags).enableObsidianIntegration;
|
||||
if (!enabled) return;
|
||||
|
||||
obsidianSettings.set(defaultSettings);
|
||||
}
|
||||
|
||||
// Helper to get file path
|
||||
export function getObsidianFilePath(noteName: string): string | undefined {
|
||||
const enabled = get(featureFlags).enableObsidianIntegration;
|
||||
if (!enabled || !noteName) return undefined;
|
||||
|
||||
return `myfiles/Fabric_obsidian/${
|
||||
new Date().toISOString().split('T')[0]
|
||||
}-${noteName.trim()}.md`;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
import { createStorageAPI } from '$lib/api/base';
|
||||
import type { Pattern } from '$lib/interfaces/pattern-interface';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import type { Pattern, PatternDescription } from '$lib/interfaces/pattern-interface';
|
||||
import { get, writable, derived } from 'svelte/store';
|
||||
import { languageStore } from './language-store';
|
||||
|
||||
// Store for all patterns
|
||||
const allPatterns = writable<Pattern[]>([]);
|
||||
|
||||
// Filtered patterns based on language
|
||||
export const patterns = derived(
|
||||
[allPatterns, languageStore],
|
||||
([$allPatterns, $language]) => {
|
||||
if (!$language) return $allPatterns;
|
||||
// If language is selected, filter out patterns of other languages
|
||||
return $allPatterns.filter(p => {
|
||||
// Keep all patterns if no language is selected
|
||||
if (!$language) return true;
|
||||
|
||||
// Check if pattern has a language prefix (e.g., en_, fr_)
|
||||
const match = p.Name.match(/^([a-z]{2})_/);
|
||||
if (!match) return true; // Keep patterns without language prefix
|
||||
|
||||
// Only filter out patterns that have a different language prefix
|
||||
const patternLang = match[1];
|
||||
return patternLang === $language;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const patterns = writable<Pattern[]>([]);
|
||||
export const systemPrompt = writable<string>('');
|
||||
export const selectedPatternName = writable<string>('');
|
||||
|
||||
export const setSystemPrompt = (prompt: string) => {
|
||||
console.log('Setting system prompt:', prompt);
|
||||
@@ -16,26 +41,47 @@ export const patternAPI = {
|
||||
|
||||
async loadPatterns() {
|
||||
try {
|
||||
// First load pattern descriptions
|
||||
const descriptionsResponse = await fetch('/static/data/pattern_descriptions.json');
|
||||
const descriptionsData = await descriptionsResponse.json();
|
||||
const descriptions = descriptionsData.patterns as PatternDescription[];
|
||||
console.log("Loaded pattern descriptions:", descriptions.length);
|
||||
|
||||
// Then load pattern names and contents
|
||||
const response = await fetch(`/api/patterns/names`);
|
||||
const data = await response.json();
|
||||
console.log("Load Patterns:", data);
|
||||
console.log("Loading patterns from API...");
|
||||
|
||||
// Create an array of promises to fetch all pattern contents
|
||||
const patternsPromises = data.map(async (pattern: string) => {
|
||||
try {
|
||||
console.log(`Loading pattern: ${pattern}`);
|
||||
const patternResponse = await fetch(`/api/patterns/${pattern}`);
|
||||
const patternData = await patternResponse.json();
|
||||
console.log(`Pattern ${pattern} content length:`, patternData.Pattern?.length || 0);
|
||||
|
||||
// Find matching description from JSON
|
||||
const desc = descriptions.find(d => d.patternName === pattern);
|
||||
if (!desc) {
|
||||
console.warn(`No description found for pattern: ${pattern}`);
|
||||
}
|
||||
|
||||
return {
|
||||
Name: pattern,
|
||||
Description: pattern.charAt(0).toUpperCase() + pattern.slice(1),
|
||||
Pattern: patternData.Pattern
|
||||
Description: desc?.description || pattern.charAt(0).toUpperCase() + pattern.slice(1),
|
||||
Pattern: patternData.Pattern || "",
|
||||
tags: desc?.tags || [] // Add tags from description
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Failed to load pattern ${pattern}:`, error);
|
||||
// Still try to get description even if pattern content fails
|
||||
const desc = descriptions.find(d => d.patternName === pattern);
|
||||
return {
|
||||
Name: pattern,
|
||||
Description: pattern.charAt(0).toUpperCase() + pattern.slice(1),
|
||||
Pattern: ""
|
||||
Description: desc?.description || pattern.charAt(0).toUpperCase() + pattern.slice(1),
|
||||
Pattern: "",
|
||||
tags: desc?.tags || [] // Add tags here too for consistency
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -43,25 +89,32 @@ export const patternAPI = {
|
||||
// Wait for all pattern contents to be fetched
|
||||
const loadedPatterns = await Promise.all(patternsPromises);
|
||||
console.log("Patterns with content:", loadedPatterns);
|
||||
patterns.set(loadedPatterns);
|
||||
allPatterns.set(loadedPatterns);
|
||||
return loadedPatterns;
|
||||
} catch (error) {
|
||||
console.error('Failed to load patterns:', error);
|
||||
patterns.set([]);
|
||||
allPatterns.set([]);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
selectPattern(patternName: string) {
|
||||
const allPatterns = get(patterns);
|
||||
const patterns = get(allPatterns);
|
||||
console.log('Selecting pattern:', patternName);
|
||||
const selectedPattern = allPatterns.find(p => p.Name === patternName);
|
||||
const selectedPattern = patterns.find(p => p.Name === patternName);
|
||||
if (selectedPattern) {
|
||||
console.log('Found pattern content:', selectedPattern.Pattern);
|
||||
setSystemPrompt(selectedPattern.Pattern.trim());
|
||||
console.log('Found pattern content (length: ' + selectedPattern.Pattern.length + '):', selectedPattern.Pattern);
|
||||
// Log the first and last 100 characters to verify content
|
||||
console.log('First 100 chars:', selectedPattern.Pattern.substring(0, 100));
|
||||
console.log('Last 100 chars:', selectedPattern.Pattern.substring(selectedPattern.Pattern.length - 100));
|
||||
console.log(`Setting system prompt with content length: ${selectedPattern.Pattern.length}`);
|
||||
console.log(`Content preview:`, selectedPattern.Pattern.substring(0, 100));
|
||||
setSystemPrompt(selectedPattern.Pattern);
|
||||
selectedPatternName.set(patternName); // Make sure this is set before setting system prompt
|
||||
} else {
|
||||
console.log('No pattern found for name:', patternName);
|
||||
setSystemPrompt('');
|
||||
selectedPatternName.set('');
|
||||
}
|
||||
console.log('System prompt store value after setting:', get(systemPrompt));
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ export const sessionAPI = {
|
||||
const response = await fetch(`/api/sessions/names`);
|
||||
const sessionNames: string[] = await response.json();
|
||||
|
||||
// Add null check and default to empty array
|
||||
if (!sessionNames) {
|
||||
sessions.set([]);
|
||||
return [];
|
||||
}
|
||||
|
||||
const sessionPromises = sessionNames.map(async (name: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/sessions/${name}`);
|
||||
@@ -44,6 +50,7 @@ export const sessionAPI = {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
selectSession(sessionName: string) {
|
||||
const allSessions = get(sessions);
|
||||
const selectedSession = allSessions.find(session => session.Name === sessionName);
|
||||
|
||||
4
web/src/lib/types/index.ts
Normal file
4
web/src/lib/types/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Pattern {
|
||||
patternName: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -1,34 +1,24 @@
|
||||
<script>
|
||||
import '../app.postcss';
|
||||
import { AppShell } from '@skeletonlabs/skeleton';
|
||||
import { Toast } from '@skeletonlabs/skeleton';
|
||||
import ToastContainer from '$lib/components/ui/toast/ToastContainer.svelte';
|
||||
import ToastContainer from '$lib/components/ui/toast/ToastContainer.svelte';
|
||||
import Footer from '$lib/components/home/Footer.svelte';
|
||||
import Header from '$lib/components/home/Header.svelte';
|
||||
import { initializeStores } from '@skeletonlabs/skeleton';
|
||||
import { initializeStores, getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { page } from '$app/stores';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
||||
import { onMount } from 'svelte';
|
||||
import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
import { toastStore } from '$lib/store/toast-store';
|
||||
|
||||
// Initialize stores
|
||||
initializeStores();
|
||||
const drawerStore = getDrawerStore();
|
||||
const toastStore = getToastStore();
|
||||
|
||||
onMount(() => {
|
||||
toastStore.trigger({
|
||||
type: 'info',
|
||||
message: "👋 Welcome to the site! Tell people about yourself and what you do.",
|
||||
background: 'variant-filled-primary',
|
||||
timeout: 3333,
|
||||
hoverable: true
|
||||
});
|
||||
toastStore.info("👋 Welcome to the site! Tell people about yourself and what you do.");
|
||||
});
|
||||
</script>
|
||||
|
||||
<Toast />
|
||||
<ToastContainer />
|
||||
|
||||
{#key $page.url.pathname}
|
||||
|
||||
46
web/src/routes/api/youtube/transcript/+server.ts
Normal file
46
web/src/routes/api/youtube/transcript/+server.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { YoutubeTranscript } from 'youtube-transcript';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json();
|
||||
console.log('Received request body:', body);
|
||||
|
||||
const { url } = body;
|
||||
if (!url) {
|
||||
return json({ error: 'URL is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
console.log('Fetching transcript for URL:', url);
|
||||
|
||||
// Extract video ID
|
||||
const match = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
|
||||
const videoId = match ? match[1] : null;
|
||||
|
||||
if (!videoId) {
|
||||
return json({ error: 'Invalid YouTube URL' }, { status: 400 });
|
||||
}
|
||||
|
||||
const transcriptItems = await YoutubeTranscript.fetchTranscript(videoId);
|
||||
const transcript = transcriptItems
|
||||
.map(item => item.text)
|
||||
.join(' ');
|
||||
|
||||
const response = {
|
||||
transcript,
|
||||
title: videoId
|
||||
};
|
||||
|
||||
console.log('Successfully fetched transcript, preparing response');
|
||||
console.log('Response (first 200 chars):', transcript.slice(0, 200) + '...');
|
||||
|
||||
return json(response);
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
return json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch transcript' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -39,4 +39,4 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -1,33 +1,162 @@
|
||||
// For the Youtube API
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getTranscript } from '$lib/services/transcriptService';
|
||||
import { YoutubeTranscript } from 'youtube-transcript';
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
try {
|
||||
const body = await request.json();
|
||||
console.log('Received request body:', body);
|
||||
console.log('\n=== Request Analysis ===');
|
||||
console.log('1. Raw request body:', JSON.stringify(body, null, 2));
|
||||
|
||||
const { url } = body;
|
||||
if (!url) {
|
||||
return json({ error: 'URL is required' }, { status: 400 });
|
||||
// Handle YouTube URL request
|
||||
if (body.url) {
|
||||
console.log('2. Processing YouTube URL:', {
|
||||
url: body.url,
|
||||
language: body.language,
|
||||
hasLanguageParam: true
|
||||
});
|
||||
|
||||
// Extract video ID
|
||||
const match = body.url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/);
|
||||
const videoId = match ? match[1] : null;
|
||||
|
||||
if (!videoId) {
|
||||
return json({ error: 'Invalid YouTube URL' }, { status: 400 });
|
||||
}
|
||||
|
||||
console.log('3. Video ID:', {
|
||||
id: videoId,
|
||||
language: body.language
|
||||
});
|
||||
|
||||
const transcriptItems = await YoutubeTranscript.fetchTranscript(videoId);
|
||||
const transcript = transcriptItems
|
||||
.map(item => item.text)
|
||||
.join(' ');
|
||||
|
||||
// Create response with transcript and language
|
||||
const response = {
|
||||
transcript,
|
||||
title: videoId,
|
||||
language: body.language
|
||||
};
|
||||
|
||||
console.log('4. Transcript processed:', {
|
||||
length: transcript.length,
|
||||
language: body.language,
|
||||
firstChars: transcript.substring(0, 50),
|
||||
responseSize: JSON.stringify(response).length
|
||||
});
|
||||
|
||||
return json(response);
|
||||
}
|
||||
|
||||
console.log('Fetching transcript for URL:', url);
|
||||
const transcriptData = await getTranscript(url);
|
||||
// Handle pattern execution request
|
||||
console.log('\n=== Server Request Analysis ===');
|
||||
console.log('1. Request overview:', {
|
||||
pattern: body.prompts?.[0]?.patternName,
|
||||
hasPrompts: !!body.prompts?.length,
|
||||
messageCount: body.messages?.length,
|
||||
isYouTube: body.url ? 'Yes' : 'No',
|
||||
language: body.language
|
||||
});
|
||||
|
||||
console.log('Successfully fetched transcript, preparing response');
|
||||
const response = json(transcriptData);
|
||||
// Ensure language instruction is present
|
||||
if (body.prompts?.[0] && body.language && body.language !== 'en') {
|
||||
const languageInstruction = `. Please use the language '${body.language}' for the output.`;
|
||||
if (!body.prompts[0].userInput?.includes(languageInstruction)) {
|
||||
body.prompts[0].userInput = (body.prompts[0].userInput || '') + languageInstruction;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the actual response being sent
|
||||
const responseText = JSON.stringify(transcriptData);
|
||||
console.log('Sending response (first 200 chars):', responseText.slice(0, 200) + '...');
|
||||
console.log('2. Language analysis:', {
|
||||
input: body.prompts?.[0]?.userInput?.substring(0, 100),
|
||||
hasLanguageInstruction: body.prompts?.[0]?.userInput?.includes('language'),
|
||||
containsFr: body.prompts?.[0]?.userInput?.includes('fr'),
|
||||
containsEn: body.prompts?.[0]?.userInput?.includes('en'),
|
||||
requestLanguage: body.language
|
||||
});
|
||||
|
||||
// Log full request for debugging
|
||||
console.log('3. Full request:', JSON.stringify(body, null, 2));
|
||||
|
||||
// Log important fields
|
||||
console.log('4. Key fields:', {
|
||||
patternName: body.prompts?.[0]?.patternName,
|
||||
inputLength: body.prompts?.[0]?.userInput?.length,
|
||||
systemPromptLength: body.prompts?.[0]?.systemPrompt?.length,
|
||||
messageCount: body.messages?.length
|
||||
});
|
||||
|
||||
console.log('5. Sending to Fabric backend...');
|
||||
const fabricResponse = await fetch('http://localhost:8080/api/chat', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
|
||||
console.log('6. Fabric response:', {
|
||||
status: fabricResponse.status,
|
||||
ok: fabricResponse.ok,
|
||||
statusText: fabricResponse.statusText
|
||||
});
|
||||
|
||||
if (!fabricResponse.ok) {
|
||||
console.error('Error from Fabric API:', {
|
||||
status: fabricResponse.status,
|
||||
statusText: fabricResponse.statusText
|
||||
});
|
||||
throw new Error(`Fabric API error: ${fabricResponse.statusText}`);
|
||||
}
|
||||
|
||||
const stream = fabricResponse.body;
|
||||
if (!stream) {
|
||||
throw new Error('No response from fabric backend');
|
||||
}
|
||||
|
||||
// Create a TransformStream to inspect the data without modifying it
|
||||
const transformStream = new TransformStream({
|
||||
transform(chunk, controller) {
|
||||
const text = new TextDecoder().decode(chunk);
|
||||
if (text.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(text.slice(6));
|
||||
console.log('Stream chunk format:', {
|
||||
type: data.type,
|
||||
format: data.format,
|
||||
contentLength: data.content?.length
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('Failed to parse stream chunk:', text);
|
||||
}
|
||||
}
|
||||
controller.enqueue(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
// Pipe through the transform stream
|
||||
const transformedStream = stream.pipeThrough(transformStream);
|
||||
|
||||
// Return the transformed stream
|
||||
const response = new Response(transformedStream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
});
|
||||
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
console.error('\n=== Error ===');
|
||||
console.error('Type:', error?.constructor?.name);
|
||||
console.error('Message:', error instanceof Error ? error.message : String(error));
|
||||
console.error('Stack:', error instanceof Error ? error.stack : 'No stack trace');
|
||||
return json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch transcript' },
|
||||
{ error: error instanceof Error ? error.message : 'Failed to process request' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ export const POST: RequestHandler = async ({ request }) => {
|
||||
// Get the absolute path to the inbox directory
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const inboxPath = join(__dirname, '..', '..', 'lib', 'content', 'inbox', filename);
|
||||
// const inboxPath = join(__dirname, '..', 'myfiles', 'inbox', filename);
|
||||
// New version using environment variables:
|
||||
// const inboxPath = join(process.env.DATA_DIR || './web/myfiles', 'inbox', filename);
|
||||
const inboxPath = join(__dirname, '..', '..', '..', 'myfiles', 'inbox', filename);
|
||||
|
||||
await writeFile(inboxPath, content, 'utf-8');
|
||||
|
||||
|
||||
105
web/src/routes/obsidian/+server.ts
Normal file
105
web/src/routes/obsidian/+server.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface ObsidianRequest {
|
||||
pattern: string;
|
||||
noteName: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
function escapeShellArg(arg: string): string {
|
||||
// Replace single quotes with '\'' and wrap in single quotes
|
||||
return `'${arg.replace(/'/g, "'\\''")}'`;
|
||||
}
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
let tempFile: string | undefined;
|
||||
|
||||
try {
|
||||
// Parse and validate request
|
||||
const body = await request.json() as ObsidianRequest;
|
||||
if (!body.pattern || !body.noteName || !body.content) {
|
||||
return json(
|
||||
{ error: 'Missing required fields: pattern, noteName, or content' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('\n=== Obsidian Request ===');
|
||||
console.log('1. Pattern:', body.pattern);
|
||||
console.log('2. Note name:', body.noteName);
|
||||
console.log('3. Content length:', body.content.length);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Format content with markdown code blocks
|
||||
const formattedContent = `\`\`\`markdown\n${body.content}\n\`\`\``;
|
||||
const escapedFormattedContent = escapeShellArg(formattedContent);
|
||||
|
||||
// Generate file name and path
|
||||
const fileName = `${new Date().toISOString().split('T')[0]}-${body.noteName}.md`;
|
||||
|
||||
const obsidianDir = 'myfiles/Fabric_obsidian';
|
||||
const filePath = `${obsidianDir}/${fileName}`;
|
||||
await execAsync(`mkdir -p "${obsidianDir}"`);
|
||||
console.log('4. Ensured Obsidian directory exists');
|
||||
|
||||
|
||||
// Create temp file
|
||||
tempFile = `/tmp/fabric-${Date.now()}.txt`;
|
||||
|
||||
// Write formatted content to temp file
|
||||
await execAsync(`echo ${escapedFormattedContent} > "${tempFile}"`);
|
||||
console.log('5. Wrote formatted content to temp file');
|
||||
|
||||
// Copy from temp file to final location (safer than direct write)
|
||||
await execAsync(`cp "${tempFile}" "${filePath}"`);
|
||||
console.log('6. Copied content to final location:', filePath);
|
||||
|
||||
// Verify file was created and has content
|
||||
const { stdout: lsOutput } = await execAsync(`ls -l "${filePath}"`);
|
||||
const { stdout: wcOutput } = await execAsync(`wc -l "${filePath}"`);
|
||||
console.log('7. File verification:', lsOutput);
|
||||
console.log('8. Line count:', wcOutput);
|
||||
|
||||
// Return success response with file details
|
||||
return json({
|
||||
success: true,
|
||||
fileName,
|
||||
filePath,
|
||||
message: `Successfully saved to ${fileName}`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n=== Error ===');
|
||||
console.error('Type:', error?.constructor?.name);
|
||||
console.error('Message:', error instanceof Error ? error.message : String(error));
|
||||
console.error('Stack:', error instanceof Error ? error.stack : 'No stack trace');
|
||||
|
||||
return json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'Failed to process request',
|
||||
details: error instanceof Error ? error.stack : undefined
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
|
||||
} finally {
|
||||
// Clean up temp file if it exists
|
||||
if (tempFile) {
|
||||
try {
|
||||
await execAsync(`rm -f "${tempFile}"`);
|
||||
console.log('9. Cleaned up temp file');
|
||||
} catch (cleanupError) {
|
||||
console.error('Failed to clean up temp file:', cleanupError);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
1721
web/static/data/pattern_descriptions.json
Normal file
1721
web/static/data/pattern_descriptions.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -38,9 +38,6 @@ const mdsvexOptions = {
|
||||
backticks: true,
|
||||
dashes: true,
|
||||
},
|
||||
layout: {
|
||||
_: './src/lib/components/posts/PostLayout.svelte',
|
||||
},
|
||||
highlight: {
|
||||
highlighter: async (code, lang) => {
|
||||
try {
|
||||
@@ -72,15 +69,25 @@ const mdsvexOptions = {
|
||||
const config = {
|
||||
extensions: ['.svelte', '.md', '.svx'],
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
adapter: adapter({
|
||||
// You can add adapter-specific options here
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
fallback: null,
|
||||
precompress: false,
|
||||
strict: true
|
||||
}),
|
||||
prerender: {
|
||||
handleHttpError: ({ path, referrer, message }) => {
|
||||
// ignore 404
|
||||
// Log the error for debugging
|
||||
console.warn(`HTTP error during prerendering: ${message}\nPath: ${path}\nReferrer: ${referrer}`);
|
||||
|
||||
// ignore 404 for specific case
|
||||
if (path === '/not-found' && referrer === '/') {
|
||||
return warn;
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise fiail
|
||||
// otherwise fail
|
||||
throw new Error(message);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -28,7 +28,31 @@ export default defineConfig({
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, '')
|
||||
timeout: 30000,
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.log('proxy error', err);
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/plain',
|
||||
});
|
||||
res.end('Something went wrong. The backend server may not be running.');
|
||||
});
|
||||
}
|
||||
},
|
||||
'^/(patterns|models|sessions)/names': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
timeout: 30000,
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.log('proxy error', err);
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
res.end(JSON.stringify({ error: 'Backend server not running', names: [] }));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -36,5 +60,5 @@ export default defineConfig({
|
||||
interval: 100,
|
||||
ignored: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/.svelte-kit/**']
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user