Development checkpoint - Web UI enhancements with complete directory structure

This commit is contained in:
jmd1010
2025-02-19 21:47:10 -05:00
parent 717eb585b5
commit 8a0f9814e6
76 changed files with 10217 additions and 654 deletions

View 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

View 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()

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -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.

View 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;
}

View File

@@ -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

View 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

View File

@@ -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"
}

View File

View 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.
```

View 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();
}
}
}

View File

@@ -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

View 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

View 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.

View 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

View File

@@ -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.

View File

@@ -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.

View 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")

View 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

View File

@@ -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

View 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

View 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

View 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

View 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.

View 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.

View File

View 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);
}
};
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) {

View 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>

View File

@@ -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>

View File

@@ -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)}

View File

@@ -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>

View File

@@ -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>

View 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>

View 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>

View 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>

View File

@@ -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(

View 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>

View File

@@ -0,0 +1,4 @@
import Checkbox from './Checkbox.svelte';
export { Checkbox };
export default Checkbox;

View 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>

View File

@@ -1,6 +1,4 @@
import Root from "./input.svelte";
export {
Root,
//
Root as Input,
};
import Input from './Input.svelte';
export { Input };
export default Input;

View File

@@ -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
)}
/>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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}

View File

@@ -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>

View File

@@ -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(() => {

View 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>

View 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
}));
}

View 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>

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}

View File

@@ -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

View 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();

View 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 };

View File

@@ -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';

View File

@@ -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
})
};
}

View 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`;
}

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -0,0 +1,4 @@
export interface Pattern {
patternName: string;
description: string;
}

View File

@@ -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}

View 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 }
);
}
};

View File

@@ -39,4 +39,4 @@
overflow: hidden;
}
</style>

View File

@@ -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 }
);
}

View File

@@ -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');

View 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);
}
}
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -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);
},
},

View File

@@ -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/**']
}
}
},
});