REMOVED: Complete AI task generation system program-wide
DELETED FILES: - src/features/tasks/aiTaskManager.js (entire AI task generation system) UPDATED FILES: - index.html: Removed AI Tasks tab from annoyance management, simplified to Import/Export only - src/core/game.js: Removed aiTaskManager initialization, event handlers, and all AI task methods - training-academy.html: Removed aiTaskManager.js script import - src/README.md: Removed aiTaskManager.js documentation - docs/ANNOYANCE_SETTINGS_DOCUMENTATION.md: Removed AI Tasks section, updated description - Start-webgame.bat: Removed Ollama service startup commands - src/styles/styles.css: Removed all AI Tasks tab styling (150+ lines) VERIFICATION: - No remaining references to aiTaskManager, AITaskManager, ollama, or Ollama in JavaScript files - Annoyance management now only contains Import/Export functionality - All AI task event handlers and UI elements removed - Documentation updated to reflect removal - Startup script no longer attempts to start Ollama service RESULT: Clean codebase with AI task generation completely removed as requested
This commit is contained in:
parent
c9c33df6fc
commit
1c129de6f6
|
|
@ -1,11 +1,4 @@
|
|||
@echo off
|
||||
REM --- Start Ollama Service ---
|
||||
echo Starting Ollama AI service...
|
||||
start /min ollama serve
|
||||
|
||||
REM --- Wait a few seconds for Ollama to initialize ---
|
||||
timeout /t 3 /nobreak >nul
|
||||
|
||||
REM --- Start the Gooner Training Academy webGame ---
|
||||
echo Launching Gooner Training Academy...
|
||||
REM If you use npm:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
# Annoyance Management System - Complete Settings Documentation
|
||||
|
||||
## Overview
|
||||
The "Annoyance Management" system (currently named, planned to be renamed to "Settings") is a configuration interface for importing and exporting message data.
|
||||
|
||||
## Tab Structure
|
||||
|
||||
### 1. 💬 Messages Tab
|
||||
**Purpose**: Manage motivational flash messages that appear during gameplay
|
||||
|
||||
#### Message Management
|
||||
- **Enable Flash Messages** (checkbox) - Master toggle for all flash messages
|
||||
- **Add Message Button** - Create new custom messages
|
||||
|
||||
#### Message Editor
|
||||
- **Message Text** (textarea, 200 char limit) - The actual message content
|
||||
- **Category** (dropdown):
|
||||
- 💪 Motivational
|
||||
- 🌟 Encouraging
|
||||
- 🏆 Achievement
|
||||
- 🔥 Persistence
|
||||
- ✨ Custom
|
||||
- **Priority** (dropdown):
|
||||
- Normal
|
||||
- High
|
||||
- Low
|
||||
|
||||
#### Message List Controls
|
||||
- **Category Filter** (dropdown) - Filter messages by category (All, Motivational, Encouraging, Achievement, Persistence, Custom)
|
||||
- **Show Disabled Messages** (checkbox) - Include disabled messages in list
|
||||
- **Message Stats Display** - Shows count of total/enabled/disabled messages
|
||||
|
||||
---
|
||||
|
||||
### 2. 🎨 Appearance Tab
|
||||
**Purpose**: Customize visual appearance of flash messages
|
||||
|
||||
#### Position & Animation
|
||||
- **Position** (dropdown):
|
||||
- Center
|
||||
- Top Center
|
||||
- Bottom Center
|
||||
- Top Left
|
||||
- Top Right
|
||||
- Bottom Left
|
||||
- Bottom Right
|
||||
- Center Left
|
||||
- Center Right
|
||||
- **Animation** (dropdown):
|
||||
- Fade
|
||||
- Slide
|
||||
- Bounce
|
||||
- Pulse
|
||||
|
||||
#### Visual Styling
|
||||
- **Font Size** (slider: 16px-48px, default: 24px)
|
||||
- **Opacity** (slider: 50%-100%, default: 90%)
|
||||
- **Text Color** (color picker, default: #ffffff)
|
||||
- **Background Color** (color picker, default: #007bff)
|
||||
|
||||
#### Controls
|
||||
- **Reset to Defaults Button** - Restore default appearance settings
|
||||
- **Preview Style Button** - Test current appearance settings
|
||||
|
||||
---
|
||||
|
||||
### 3. ⚡ Behavior Tab
|
||||
**Purpose**: Configure timing and behavior of flash messages
|
||||
|
||||
#### Core Behavior Settings
|
||||
- **Focus Interruption Chance** (slider: 0%-50%, default: 0%)
|
||||
- Chance for focus-hold interruptions during scenario adventures
|
||||
- 0% = disabled, max 50%
|
||||
- **Display Duration** (slider: 1.0s-10.0s, default: 3.0s)
|
||||
- **Interval Between Messages** (slider: 10s-300s, default: 45s)
|
||||
- **Random Variation** (slider: 0s-30s, default: ±5s)
|
||||
- Adds random time variation to prevent predictability
|
||||
|
||||
#### Advanced Options
|
||||
- **Enable Event-Based Messages** (checkbox, default: checked)
|
||||
- Show special messages for task completion, streaks, etc.
|
||||
- **Pause Timer on Message Hover** (checkbox, default: unchecked)
|
||||
- Pause message fade when hovering (useful for reading)
|
||||
|
||||
#### Testing
|
||||
- **Test Current Settings Button** - Preview behavior with current settings
|
||||
|
||||
---
|
||||
|
||||
### 4. 🖼️ Popup Images Tab
|
||||
**Purpose**: Configure punishment popups that appear when tasks are skipped
|
||||
|
||||
#### Master Control
|
||||
- **Enable Punishment Popups** (toggle switch)
|
||||
|
||||
#### Image Count Settings
|
||||
- **Count Mode** (dropdown):
|
||||
- Fixed Amount
|
||||
- Random (1-10)
|
||||
- Custom Range
|
||||
- **Number of Images** (slider: 1-40, default: 3) - for Fixed mode
|
||||
- **Custom Range** (number inputs) - Min: 1-20, Max: 2-40
|
||||
|
||||
#### Display Duration Settings
|
||||
- **Duration Mode** (dropdown):
|
||||
- Fixed Duration
|
||||
- Random (5-15s)
|
||||
- Custom Range
|
||||
- **Duration** (slider: 3s-30s, default: 8s) - for Fixed mode
|
||||
- **Custom Range** (number inputs) - Min: 2-20s, Max: 5-60s
|
||||
|
||||
#### Positioning & Layout
|
||||
- **Layout Style** (dropdown):
|
||||
- Random Positions
|
||||
- Cascading
|
||||
- Grid Layout
|
||||
- Center (stacked)
|
||||
- **Allow Overlapping** (toggle switch)
|
||||
|
||||
#### Size Settings
|
||||
- **Max Viewport Width** (slider: 20%-60%, default: 35%)
|
||||
- **Max Viewport Height** (slider: 20%-60%, default: 40%)
|
||||
- **Min Width** (number input: 150-400px, default: 200px)
|
||||
- **Max Width** (number input: 300-800px, default: 500px)
|
||||
- **Min Height** (number input: 100-300px, default: 150px)
|
||||
- **Max Height** (number input: 200-600px, default: 400px)
|
||||
|
||||
#### Visual Effects
|
||||
- **Fade In/Out Animation** (toggle switch)
|
||||
- **Blur Background** (toggle switch)
|
||||
- **Show Countdown Timer** (toggle switch)
|
||||
- **Prevent Manual Close** (toggle switch)
|
||||
|
||||
#### Testing & Status
|
||||
- **Test 1 Popup Button** - Test single popup
|
||||
- **Test Multiple Button** - Test multiple popups
|
||||
- **Clear All Button** - Remove all active popups
|
||||
- **Available Images Count** - Display count of available images
|
||||
- **Active Popups Count** - Display count of currently active popups
|
||||
|
||||
---
|
||||
|
||||
### 5. 📁 Import/Export
|
||||
**Purpose**: Backup, share, and manage message configurations
|
||||
|
||||
#### Export Options
|
||||
- **Export All Messages Button** - Export complete message set
|
||||
- **Export Enabled Only Button** - Export only active messages
|
||||
- **Export Custom Only Button** - Export only user-created messages
|
||||
|
||||
#### Import Options
|
||||
- **Import Messages Button** - Load messages from file
|
||||
- **File Input** - JSON file selection
|
||||
- **Import Mode** (radio buttons):
|
||||
- Merge with existing (default)
|
||||
- Replace all messages
|
||||
|
||||
#### Reset Options
|
||||
- **Reset to Default Messages Button** - Restore original message set
|
||||
- **Clear All Messages Button** - Remove all messages (⚠️ Warning: Cannot be undone)
|
||||
|
||||
---
|
||||
|
||||
## Global Controls
|
||||
- **Back to Start Button** - Return to main menu
|
||||
- **Save All Settings Button** - Apply and save all configuration changes
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
- All settings are persisted to localStorage
|
||||
- Flash messages use the FlashMessageManager class
|
||||
- Popup images use the PopupImageManager class
|
||||
|
||||
- Import/Export uses JSON format for data portability
|
||||
- Settings are organized in a tabbed interface for better UX
|
||||
|
||||
## Recommended Improvements
|
||||
1. **Rename "Annoyance" to "Settings"** - More user-friendly terminology
|
||||
2. **Add tooltips** - Better explain complex settings
|
||||
3. **Preview functionality** - Real-time preview for all visual settings
|
||||
4. **Preset configurations** - Quick setup options for different use cases
|
||||
5. **Advanced scheduling** - Time-based message scheduling
|
||||
597
index.html
597
index.html
|
|
@ -802,561 +802,12 @@
|
|||
<!-- Annoyance Management Screen -->
|
||||
<div id="annoyance-management-screen" class="screen">
|
||||
<h2>😈 Annoyance Management</h2>
|
||||
<p>Configure flash messages and motivational features to enhance your experience!</p>
|
||||
<p>Manage message import/export and data settings.</p>
|
||||
<p><strong>Note:</strong> Flash messages and popup images have been moved to Quick Play settings.</p>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="annoyance-tabs">
|
||||
<button id="messages-tab" class="annoyance-tab active">💬 Messages</button>
|
||||
<button id="appearance-tab" class="annoyance-tab">🎨 Appearance</button>
|
||||
<button id="behavior-tab" class="annoyance-tab">⚡ Behavior</button>
|
||||
<button id="popup-images-tab" class="annoyance-tab">🖼️ Popup Images</button>
|
||||
<button id="ai-tasks-tab" class="annoyance-tab">🤖 AI Tasks</button>
|
||||
<button id="import-export-tab" class="annoyance-tab">📁 Import/Export</button>
|
||||
</div>
|
||||
|
||||
<!-- Messages Tab -->
|
||||
<div id="messages-tab-content" class="annoyance-tab-content active">
|
||||
<div class="annoyance-section">
|
||||
<div class="section-header">
|
||||
<h3>💬 Message Management</h3>
|
||||
<div class="header-controls">
|
||||
<label>
|
||||
<input type="checkbox" id="flash-messages-enabled" checked>
|
||||
Enable Flash Messages
|
||||
</label>
|
||||
<button id="add-new-message-btn" class="btn btn-success btn-small">+ Add Message</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Editor -->
|
||||
<div id="message-editor" class="message-editor" style="display: none;">
|
||||
<div class="editor-header">
|
||||
<h4 id="editor-title">Add New Message</h4>
|
||||
<button id="close-editor-btn" class="btn btn-outline btn-small">✕ Close</button>
|
||||
</div>
|
||||
<div class="editor-form">
|
||||
<div class="form-group">
|
||||
<label>Message Text:</label>
|
||||
<textarea id="message-text" placeholder="Enter your motivational message..." maxlength="200" rows="3"></textarea>
|
||||
<div class="char-counter">
|
||||
<span id="char-count">0</span>/200 characters
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Category:</label>
|
||||
<select id="message-category">
|
||||
<option value="motivational">💪 Motivational</option>
|
||||
<option value="encouraging">🌟 Encouraging</option>
|
||||
<option value="achievement">🏆 Achievement</option>
|
||||
<option value="persistence">🔥 Persistence</option>
|
||||
<option value="custom">✨ Custom</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Priority:</label>
|
||||
<select id="message-priority">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="high">High</option>
|
||||
<option value="low">Low</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<button id="save-message-btn" class="btn btn-primary">Save Message</button>
|
||||
<button id="preview-current-message-btn" class="btn btn-info">Preview</button>
|
||||
<button id="cancel-edit-btn" class="btn btn-secondary">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message List -->
|
||||
<div class="message-list-section">
|
||||
<div class="list-header">
|
||||
<div class="list-filters">
|
||||
<label>Filter by Category:
|
||||
<select id="category-filter">
|
||||
<option value="all">All Categories</option>
|
||||
<option value="motivational">💪 Motivational</option>
|
||||
<option value="encouraging">🌟 Encouraging</option>
|
||||
<option value="achievement">🏆 Achievement</option>
|
||||
<option value="persistence">🔥 Persistence</option>
|
||||
<option value="custom">✨ Custom</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" id="show-disabled-messages"> Show Disabled
|
||||
</label>
|
||||
</div>
|
||||
<div class="list-stats">
|
||||
<span id="message-stats">20 messages (18 enabled, 2 disabled)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="message-list" class="message-list">
|
||||
<!-- Messages will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Appearance Tab -->
|
||||
<div id="appearance-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>🎨 Visual Appearance</h3>
|
||||
<div class="appearance-controls">
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>Position:</label>
|
||||
<select id="message-position">
|
||||
<option value="center">Center</option>
|
||||
<option value="top-center">Top Center</option>
|
||||
<option value="bottom-center">Bottom Center</option>
|
||||
<option value="top-left">Top Left</option>
|
||||
<option value="top-right">Top Right</option>
|
||||
<option value="bottom-left">Bottom Left</option>
|
||||
<option value="bottom-right">Bottom Right</option>
|
||||
<option value="center-left">Center Left</option>
|
||||
<option value="center-right">Center Right</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Animation:</label>
|
||||
<select id="animation-style">
|
||||
<option value="fade">Fade</option>
|
||||
<option value="slide">Slide</option>
|
||||
<option value="bounce">Bounce</option>
|
||||
<option value="pulse">Pulse</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>Font Size: <span id="font-size-display">24px</span></label>
|
||||
<input type="range" id="font-size" min="16" max="48" value="24" step="2">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Opacity: <span id="opacity-display">90%</span></label>
|
||||
<input type="range" id="message-opacity" min="50" max="100" value="90" step="5">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>Text Color:</label>
|
||||
<input type="color" id="text-color" value="#ffffff">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Background Color:</label>
|
||||
<input type="color" id="background-color" value="#007bff">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="reset-appearance-btn" class="btn btn-outline">Reset to Defaults</button>
|
||||
<button id="preview-appearance-btn" class="btn btn-info">Preview Style</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Behavior Tab -->
|
||||
<div id="behavior-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>⚡ Behavior Settings</h3>
|
||||
<div class="behavior-controls">
|
||||
<div class="control-group">
|
||||
<label>🧘 Focus Interruption Chance: <span id="focus-interruption-display">0%</span></label>
|
||||
<input type="range" id="focus-interruption-chance" min="0" max="50" value="0" step="5">
|
||||
<small class="help-text">Chance for focus-hold interruptions during scenario adventures (0% = disabled, max 50%)</small>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Display Duration: <span id="duration-display">3.0s</span></label>
|
||||
<input type="range" id="display-duration" min="1000" max="10000" value="3000" step="500">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Interval Between Messages: <span id="interval-display">45s</span></label>
|
||||
<input type="range" id="interval-delay" min="10000" max="300000" value="45000" step="5000">
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>Random Variation: <span id="variation-display">±5s</span></label>
|
||||
<input type="range" id="time-variation" min="0" max="30000" value="5000" step="1000">
|
||||
<small class="help-text">Adds random time variation to prevent predictability</small>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
<div class="control-group">
|
||||
<label>
|
||||
<input type="checkbox" id="event-based-messages" checked>
|
||||
Enable Event-Based Messages
|
||||
</label>
|
||||
<small class="help-text">Show special messages for task completion, streaks, etc.</small>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>
|
||||
<input type="checkbox" id="pause-on-hover">
|
||||
Pause Timer on Message Hover
|
||||
</label>
|
||||
<small class="help-text">Pause message fade when hovering (useful for reading)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<button id="test-behavior-btn" class="btn btn-success">Test Current Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popup Images Tab -->
|
||||
<div id="popup-images-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>🖼️ Punishment Popups</h3>
|
||||
<p class="help-text">Configure consequence images that appear when tasks are skipped</p>
|
||||
|
||||
<!-- Enable/Disable -->
|
||||
<div class="control-section">
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-images-enabled" />
|
||||
<span class="switch"></span>
|
||||
Enable Punishment Popups
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Count Settings -->
|
||||
<div class="control-section">
|
||||
<h4>📊 Number of Images</h4>
|
||||
<div class="control-group">
|
||||
<label for="popup-count-mode">Count Mode:</label>
|
||||
<select id="popup-count-mode">
|
||||
<option value="fixed">Fixed Amount</option>
|
||||
<option value="random">Random (1-10)</option>
|
||||
<option value="range">Custom Range</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="popup-fixed-count" class="control-group">
|
||||
<label for="popup-image-count">Number of Images:</label>
|
||||
<input type="range" id="popup-image-count" min="1" max="40" value="3" />
|
||||
<span id="popup-image-count-value">3</span>
|
||||
</div>
|
||||
|
||||
<div id="popup-range-count" class="control-group" style="display: none;">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-count">Minimum:</label>
|
||||
<input type="number" id="popup-min-count" min="1" max="20" value="2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-count">Maximum:</label>
|
||||
<input type="number" id="popup-max-count" min="2" max="40" value="5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Display Duration Settings -->
|
||||
<div class="control-section">
|
||||
<h4>⏱️ Display Duration</h4>
|
||||
<div class="control-group">
|
||||
<label for="popup-duration-mode">Duration Mode:</label>
|
||||
<select id="popup-duration-mode">
|
||||
<option value="fixed">Fixed Duration</option>
|
||||
<option value="random">Random (5-15s)</option>
|
||||
<option value="range">Custom Range</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="popup-fixed-duration" class="control-group">
|
||||
<label for="popup-display-duration">Duration (seconds):</label>
|
||||
<input type="range" id="popup-display-duration" min="3" max="30" value="8" />
|
||||
<span id="popup-display-duration-value">8s</span>
|
||||
</div>
|
||||
|
||||
<div id="popup-range-duration" class="control-group" style="display: none;">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-duration">Min (seconds):</label>
|
||||
<input type="number" id="popup-min-duration" min="2" max="20" value="5" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-duration">Max (seconds):</label>
|
||||
<input type="number" id="popup-max-duration" min="5" max="60" value="15" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Positioning & Appearance -->
|
||||
<div class="control-section">
|
||||
<h4>🎯 Positioning</h4>
|
||||
<div class="control-group">
|
||||
<label for="popup-positioning">Layout Style:</label>
|
||||
<select id="popup-positioning">
|
||||
<option value="random">Random Positions</option>
|
||||
<option value="cascade">Cascading</option>
|
||||
<option value="grid">Grid Layout</option>
|
||||
<option value="center">Centered (stacked)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-allow-overlap" />
|
||||
<span class="switch"></span>
|
||||
Allow Overlapping
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Size Settings -->
|
||||
<div class="control-section">
|
||||
<h4>📏 Size Settings</h4>
|
||||
<p class="help-text">Popups automatically size to match image proportions within these limits</p>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="popup-viewport-width">Max Viewport Width:</label>
|
||||
<input type="range" id="popup-viewport-width" min="20" max="60" value="35" />
|
||||
<span id="popup-viewport-width-value">35%</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="popup-viewport-height">Max Viewport Height:</label>
|
||||
<input type="range" id="popup-viewport-height" min="20" max="60" value="40" />
|
||||
<span id="popup-viewport-height-value">40%</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-width">Min Width (px):</label>
|
||||
<input type="number" id="popup-min-width" min="150" max="400" value="200" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-width">Max Width (px):</label>
|
||||
<input type="number" id="popup-max-width" min="300" max="800" value="500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<div class="range-inputs">
|
||||
<div>
|
||||
<label for="popup-min-height">Min Height (px):</label>
|
||||
<input type="number" id="popup-min-height" min="100" max="300" value="150" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="popup-max-height">Max Height (px):</label>
|
||||
<input type="number" id="popup-max-height" min="200" max="600" value="400" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visual Effects -->
|
||||
<div class="control-section">
|
||||
<h4>✨ Visual Effects</h4>
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-fade-animation" />
|
||||
<span class="switch"></span>
|
||||
Fade In/Out Animation
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-blur-background" />
|
||||
<span class="switch"></span>
|
||||
Blur Background
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-show-timer" />
|
||||
<span class="switch"></span>
|
||||
Show Countdown Timer
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="popup-prevent-close" />
|
||||
<span class="switch"></span>
|
||||
Prevent Manual Close
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test & Preview -->
|
||||
<div class="control-section">
|
||||
<h4>🧪 Testing</h4>
|
||||
<div class="test-buttons">
|
||||
<button id="test-popup-single" class="btn btn-info">Test 1 Popup</button>
|
||||
<button id="test-popup-multiple" class="btn btn-primary">Test Multiple</button>
|
||||
<button id="clear-all-popups" class="btn btn-danger">Clear All</button>
|
||||
</div>
|
||||
<p class="help-text">Test your popup settings to see how they look</p>
|
||||
<div id="popup-warning" class="warning-text" style="display: none;">
|
||||
⚠️ High popup counts (>20) may impact performance and visibility
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status & Info -->
|
||||
<div class="control-section">
|
||||
<div class="info-display">
|
||||
<div class="info-item">
|
||||
<span class="info-label">Available Images:</span>
|
||||
<span id="available-images-count">0</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Active Popups:</span>
|
||||
<span id="active-popups-count">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI Tasks Tab -->
|
||||
<div id="ai-tasks-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3>🤖 AI Task Generation</h3>
|
||||
<p class="help-text">Let AI create personalized edging tasks using your local Ollama installation</p>
|
||||
|
||||
<!-- Connection Status -->
|
||||
<div class="control-section">
|
||||
<h4>📡 Connection Status</h4>
|
||||
<div class="ai-status-display">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Ollama Service:</span>
|
||||
<span id="ollama-status" class="status-value">Checking...</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Available Models:</span>
|
||||
<span id="models-count" class="status-value">0</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Current Model:</span>
|
||||
<span id="current-model" class="status-value">None</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="test-ai-connection" class="btn btn-info">Test Connection</button>
|
||||
</div>
|
||||
|
||||
<!-- AI Configuration -->
|
||||
<div class="control-section">
|
||||
<h4>⚙️ AI Configuration</h4>
|
||||
|
||||
<div class="control-group">
|
||||
<label class="switch-label">
|
||||
<input type="checkbox" id="ai-tasks-enabled" />
|
||||
<span class="switch"></span>
|
||||
Enable AI Task Generation
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-model-select">Model Selection:</label>
|
||||
<select id="ai-model-select">
|
||||
<option value="">Select a model...</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-temperature">Creativity (Temperature):</label>
|
||||
<input type="range" id="ai-temperature" min="0.1" max="2.0" step="0.1" value="0.8" />
|
||||
<span id="ai-temperature-value">0.8</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-max-tokens">Max Response Length:</label>
|
||||
<input type="range" id="ai-max-tokens" min="100" max="500" step="50" value="300" />
|
||||
<span id="ai-max-tokens-value">300</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Preferences -->
|
||||
<div class="control-section">
|
||||
<h4>👤 Your Preferences</h4>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-experience">Experience Level:</label>
|
||||
<select id="ai-experience">
|
||||
<option value="beginner">Beginner</option>
|
||||
<option value="intermediate" selected>Intermediate</option>
|
||||
<option value="advanced">Advanced</option>
|
||||
<option value="expert">Expert</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-intensity">Default Intensity:</label>
|
||||
<select id="ai-intensity">
|
||||
<option value="low">Low</option>
|
||||
<option value="medium" selected>Medium</option>
|
||||
<option value="high">High</option>
|
||||
<option value="extreme">Extreme</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-duration">Preferred Duration (minutes):</label>
|
||||
<input type="range" id="ai-duration" min="3" max="30" value="5" />
|
||||
<span id="ai-duration-value">5</span>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="ai-style">Instruction Style:</label>
|
||||
<select id="ai-style">
|
||||
<option value="instructional" selected>Instructional</option>
|
||||
<option value="descriptive">Descriptive</option>
|
||||
<option value="commanding">Commanding</option>
|
||||
<option value="encouraging">Encouraging</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Testing & Preview -->
|
||||
<div class="control-section">
|
||||
<h4>🧪 Testing</h4>
|
||||
<div class="ai-test-buttons">
|
||||
<button id="generate-test-task" class="btn btn-primary">Generate Test Task</button>
|
||||
<button id="generate-consequence-task" class="btn btn-danger">Generate Consequence</button>
|
||||
<button id="clear-test-output" class="btn btn-secondary">Clear Output</button>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label for="test-task-output">Generated Task Preview:</label>
|
||||
<div id="test-task-output" class="task-preview">
|
||||
Click "Generate Test Task" to see AI-generated content...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Installation Help -->
|
||||
<div class="control-section">
|
||||
<h4>📚 Setup Help</h4>
|
||||
<div class="help-content">
|
||||
<p><strong>Need to install Ollama?</strong></p>
|
||||
<ol>
|
||||
<li>Download from <a href="https://ollama.ai" target="_blank">ollama.ai</a></li>
|
||||
<li>Install recommended NSFW models:</li>
|
||||
<ul>
|
||||
<li><code>ollama pull dolphin-mistral:7b</code></li>
|
||||
<li><code>ollama pull wizardlm-uncensored:7b</code></li>
|
||||
</ul>
|
||||
<li>Ensure Ollama service is running</li>
|
||||
<li>Click "Test Connection" above</li>
|
||||
</ol>
|
||||
<p class="help-text">AI tasks are generated locally for complete privacy!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import/Export Tab -->
|
||||
<div id="import-export-tab-content" class="annoyance-tab-content">
|
||||
<div class="annoyance-section">
|
||||
<h3><EFBFBD> Import & Export</h3>
|
||||
<!-- Import/Export Content -->
|
||||
<div class="annoyance-section">
|
||||
<h3>📁 Import & Export</h3>
|
||||
<div class="import-export-controls">
|
||||
<div class="control-section">
|
||||
<h4>💾 Export Messages</h4>
|
||||
|
|
@ -1859,7 +1310,7 @@
|
|||
<!-- Script Loading Order -->
|
||||
<script src="src/features/ui/flashMessageManager.js"></script>
|
||||
<script src="src/features/images/popupImageManager.js"></script>
|
||||
<script src="src/features/tasks/aiTaskManager.js"></script>
|
||||
|
||||
<script src="src/features/audio/audioManager.js"></script>
|
||||
<script src="src/features/stats/playerStats.js"></script>
|
||||
<script src="src/core/gameModeManager.js"></script>
|
||||
|
|
@ -4960,7 +4411,9 @@
|
|||
localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos));
|
||||
|
||||
// Show success message
|
||||
showFlashMessage(`📸 Photo deleted successfully!`, 'success');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show(`📸 Photo deleted successfully!`, 'info');
|
||||
}
|
||||
|
||||
// Refresh the photo galleries
|
||||
setupLibraryGalleryTab();
|
||||
|
|
@ -5024,7 +4477,9 @@
|
|||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
showFlashMessage(`📥 Photo downloaded: ${filename}`, 'success');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show(`📥 Photo downloaded: ${filename}`, 'info');
|
||||
}
|
||||
console.log(`📥 Downloaded photo: ${filename}`);
|
||||
}
|
||||
|
||||
|
|
@ -5034,7 +4489,9 @@
|
|||
const capturedPhotos = JSON.parse(localStorage.getItem('capturedPhotos') || '[]');
|
||||
|
||||
if (selectedCheckboxes.length === 0) {
|
||||
showFlashMessage('⚠️ No photos selected for download', 'warning');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show('⚠️ No photos selected for download', 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -5046,7 +4503,9 @@
|
|||
}
|
||||
|
||||
// Multiple photos - create zip
|
||||
showFlashMessage('📦 Creating zip file...', 'info');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show('📦 Creating zip file...', 'info');
|
||||
}
|
||||
|
||||
try {
|
||||
// Create zip file (using JSZip if available, otherwise download individually)
|
||||
|
|
@ -5073,7 +4532,9 @@
|
|||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
showFlashMessage(`📥 Downloaded ${selectedCheckboxes.length} photos as zip file`, 'success');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show(`📥 Downloaded ${selectedCheckboxes.length} photos as zip file`, 'info');
|
||||
}
|
||||
} else {
|
||||
// Fallback: download individually
|
||||
selectedCheckboxes.forEach((checkbox, downloadIndex) => {
|
||||
|
|
@ -5083,11 +4544,15 @@
|
|||
}, downloadIndex * 100); // Stagger downloads
|
||||
});
|
||||
|
||||
showFlashMessage(`📥 Downloading ${selectedCheckboxes.length} photos individually`, 'info');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show(`📥 Downloading ${selectedCheckboxes.length} photos individually`, 'info');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Download error:', error);
|
||||
showFlashMessage('❌ Error creating download', 'error');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show('❌ Error creating download', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5096,7 +4561,9 @@
|
|||
const selectedCheckboxes = document.querySelectorAll('.photo-select:checked');
|
||||
|
||||
if (selectedCheckboxes.length === 0) {
|
||||
showFlashMessage('⚠️ No photos selected for deletion', 'warning');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show('⚠️ No photos selected for deletion', 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -5117,7 +4584,9 @@
|
|||
localStorage.setItem('capturedPhotos', JSON.stringify(capturedPhotos));
|
||||
|
||||
// Show success message
|
||||
showFlashMessage(`🗑️ Successfully deleted ${indicesToDelete.length} photos!`, 'success');
|
||||
if (window.game && window.game.flashMessageManager) {
|
||||
window.game.flashMessageManager.show(`🗑️ Successfully deleted ${indicesToDelete.length} photos!`, 'info');
|
||||
}
|
||||
|
||||
// Refresh the photo galleries
|
||||
setupLibraryGalleryTab();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
144
porn-cinema.html
144
porn-cinema.html
|
|
@ -33,13 +33,14 @@
|
|||
<div class="shortcut-item"><kbd>←</kbd><kbd>→</kbd> Seek ±10s</div>
|
||||
<div class="shortcut-item"><kbd>↑</kbd><kbd>↓</kbd> Volume ±10%</div>
|
||||
<div class="shortcut-item"><kbd>F</kbd> Fullscreen</div>
|
||||
<div class="shortcut-item"><kbd>T</kbd> Theater Mode</div>
|
||||
<div class="shortcut-item"><kbd>M</kbd> Mute/Unmute</div>
|
||||
<div class="shortcut-item"><kbd>1-4</kbd> Quality</div>
|
||||
<div class="shortcut-item"><kbd>Enter</kbd> Add to Playlist</div>
|
||||
<div class="shortcut-item"><kbd>N</kbd> Next Video</div>
|
||||
<div class="shortcut-item"><kbd>P</kbd> Previous Video</div>
|
||||
<div class="shortcut-item"><kbd>S</kbd> Shuffle Playlist</div>
|
||||
<div class="shortcut-item"><kbd>Escape</kbd> Exit Fullscreen</div>
|
||||
<div class="shortcut-item"><kbd>Escape</kbd> Exit Theater/Fullscreen</div>
|
||||
<div class="shortcut-item"><kbd>?</kbd> Toggle This Help</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -274,7 +275,7 @@
|
|||
break;
|
||||
}
|
||||
|
||||
console.log(`⏳ Waiting for desktop file manager to initialize... (${retries + 1}/${maxRetries})`);
|
||||
// Waiting for desktop file manager to initialize...
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
retries++;
|
||||
}
|
||||
|
|
@ -394,12 +395,151 @@
|
|||
}, 100);
|
||||
}
|
||||
|
||||
// Theater mode functionality
|
||||
let theaterModeActive = false;
|
||||
let headerHideTimeout = null;
|
||||
let escapeHintTimeout = null;
|
||||
|
||||
function createTheaterModeElements() {
|
||||
// Create header hover zone for theater mode
|
||||
const hoverZone = document.createElement('div');
|
||||
hoverZone.className = 'header-hover-zone';
|
||||
hoverZone.id = 'header-hover-zone';
|
||||
document.body.appendChild(hoverZone);
|
||||
|
||||
// Create escape hint
|
||||
const escapeHint = document.createElement('div');
|
||||
escapeHint.className = 'theater-escape-hint';
|
||||
escapeHint.id = 'theater-escape-hint';
|
||||
escapeHint.innerHTML = 'Press <kbd>T</kbd> or <kbd>Esc</kbd> to exit theater mode';
|
||||
document.body.appendChild(escapeHint);
|
||||
}
|
||||
|
||||
function setupTheaterMouseTracking() {
|
||||
const header = document.querySelector('.cinema-header');
|
||||
const hoverZone = document.getElementById('header-hover-zone');
|
||||
const escapeHint = document.getElementById('theater-escape-hint');
|
||||
|
||||
if (!hoverZone || !header) return;
|
||||
|
||||
// Show header when mouse enters hover zone
|
||||
hoverZone.addEventListener('mouseenter', () => {
|
||||
if (theaterModeActive) {
|
||||
header.classList.remove('auto-hide');
|
||||
header.classList.add('show-on-hover');
|
||||
clearTimeout(headerHideTimeout);
|
||||
}
|
||||
});
|
||||
|
||||
// Hide header when mouse leaves hover zone
|
||||
hoverZone.addEventListener('mouseleave', () => {
|
||||
if (theaterModeActive) {
|
||||
headerHideTimeout = setTimeout(() => {
|
||||
header.classList.remove('show-on-hover');
|
||||
header.classList.add('auto-hide');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-hide escape hint after 5 seconds
|
||||
if (escapeHint) {
|
||||
escapeHintTimeout = setTimeout(() => {
|
||||
escapeHint.classList.add('fade-out');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheaterMode() {
|
||||
theaterModeActive = !theaterModeActive;
|
||||
const body = document.body;
|
||||
const sidebar = document.querySelector('.cinema-sidebar');
|
||||
const librarySection = document.querySelector('.video-library-section');
|
||||
const mainContentArea = document.querySelector('.main-content-area');
|
||||
const cinemaMain = document.querySelector('.cinema-main');
|
||||
const theaterBtn = document.getElementById('theater-mode');
|
||||
const theaterBtnControl = document.getElementById('theater-mode-btn');
|
||||
const header = document.querySelector('.cinema-header');
|
||||
const escapeHint = document.getElementById('theater-escape-hint');
|
||||
|
||||
if (theaterModeActive) {
|
||||
// Enter theater mode
|
||||
body.classList.add('theater-mode-active');
|
||||
sidebar.style.display = 'none';
|
||||
librarySection.style.display = 'none';
|
||||
cinemaMain.style.gridTemplateColumns = '1fr';
|
||||
mainContentArea.style.padding = '0';
|
||||
|
||||
// Create theater mode elements if they don't exist
|
||||
if (!document.getElementById('header-hover-zone')) {
|
||||
createTheaterModeElements();
|
||||
}
|
||||
|
||||
// Setup mouse tracking for header auto-hide
|
||||
setupTheaterMouseTracking();
|
||||
|
||||
// Initially hide header after 3 seconds
|
||||
headerHideTimeout = setTimeout(() => {
|
||||
if (header) {
|
||||
header.classList.add('auto-hide');
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
// Update button text
|
||||
if (theaterBtn) theaterBtn.innerHTML = '🎭 Exit Theater';
|
||||
if (theaterBtnControl) theaterBtnControl.innerHTML = '🎭';
|
||||
|
||||
console.log('🎭 Theater mode activated - Video player fills entire window');
|
||||
} else {
|
||||
// Exit theater mode
|
||||
body.classList.remove('theater-mode-active');
|
||||
sidebar.style.display = 'flex';
|
||||
librarySection.style.display = 'block';
|
||||
cinemaMain.style.gridTemplateColumns = '1fr 320px';
|
||||
mainContentArea.style.padding = '20px';
|
||||
|
||||
// Clean up theater mode elements
|
||||
clearTimeout(headerHideTimeout);
|
||||
clearTimeout(escapeHintTimeout);
|
||||
if (header) {
|
||||
header.classList.remove('auto-hide', 'show-on-hover');
|
||||
}
|
||||
if (escapeHint) {
|
||||
escapeHint.classList.remove('fade-out');
|
||||
}
|
||||
|
||||
// Update button text
|
||||
if (theaterBtn) theaterBtn.innerHTML = '🎭 Theater';
|
||||
if (theaterBtnControl) theaterBtnControl.innerHTML = '🎭';
|
||||
|
||||
console.log('🎭 Theater mode deactivated - Normal layout restored');
|
||||
}
|
||||
}
|
||||
|
||||
// Theater mode button event listeners
|
||||
document.getElementById('theater-mode').addEventListener('click', toggleTheaterMode);
|
||||
document.getElementById('theater-mode-btn').addEventListener('click', toggleTheaterMode);
|
||||
|
||||
// Keyboard shortcut to toggle help
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === '?' || (e.shiftKey && e.key === '/')) {
|
||||
const help = document.getElementById('shortcuts-help');
|
||||
help.style.display = help.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Theater mode keyboard shortcuts
|
||||
if (e.key.toLowerCase() === 't' && !e.ctrlKey && !e.altKey && !e.shiftKey) {
|
||||
// Only toggle if not typing in an input field
|
||||
if (!['INPUT', 'TEXTAREA'].includes(e.target.tagName)) {
|
||||
toggleTheaterMode();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Escape key to exit theater mode
|
||||
if (e.key === 'Escape' && theaterModeActive) {
|
||||
toggleTheaterMode();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Sidebar tab functionality
|
||||
|
|
|
|||
2558
quick-play.html
2558
quick-play.html
File diff suppressed because it is too large
Load Diff
|
|
@ -18,7 +18,7 @@ Organized by functional area, each feature is self-contained:
|
|||
|
||||
#### `/features/tasks` - Task Management
|
||||
- **`interactiveTaskManager.js`** - Task execution, photography integration, and scenario handling
|
||||
- **`aiTaskManager.js`** - AI-powered task generation using local Ollama models
|
||||
|
||||
|
||||
#### `/features/audio` - Audio System
|
||||
- **`audioManager.js`** - Background music, sound effects, and audio controls
|
||||
|
|
|
|||
305
src/core/game.js
305
src/core/game.js
|
|
@ -113,13 +113,9 @@ class TaskChallengeGame {
|
|||
window.popupImageManager = this.popupImageManager; // Make available globally for HTML onclick handlers
|
||||
this.updateLoadingProgress(60, 'Popup system loaded...');
|
||||
|
||||
// Initialize AI Task Generation System
|
||||
this.aiTaskManager = new AITaskManager(this.dataManager);
|
||||
this.updateLoadingProgress(65, 'AI task manager loaded...');
|
||||
|
||||
// Initialize Audio Management System
|
||||
this.audioManager = new AudioManager(this.dataManager, this);
|
||||
this.updateLoadingProgress(70, 'Audio manager loaded...');
|
||||
this.updateLoadingProgress(65, 'Audio manager loaded...');
|
||||
|
||||
// Initialize Game Mode Manager
|
||||
this.gameModeManager = new GameModeManager();
|
||||
|
|
@ -7386,18 +7382,7 @@ TaskChallengeGame.prototype.setupAnnoyanceManagementEventListeners = function()
|
|||
|
||||
// Tab navigation
|
||||
document.getElementById('messages-tab').onclick = () => this.showAnnoyanceTab('messages');
|
||||
document.getElementById('appearance-tab').onclick = () => this.showAnnoyanceTab('appearance');
|
||||
document.getElementById('behavior-tab').onclick = () => this.showAnnoyanceTab('behavior');
|
||||
document.getElementById('popup-images-tab').onclick = () => this.showAnnoyanceTab('popup-images');
|
||||
document.getElementById('import-export-tab').onclick = () => this.showAnnoyanceTab('import-export');
|
||||
document.getElementById('ai-tasks-tab').onclick = () => this.showAnnoyanceTab('ai-tasks');
|
||||
|
||||
this.setupMessagesTabListeners();
|
||||
this.setupAppearanceTabListeners();
|
||||
this.setupBehaviorTabListeners();
|
||||
this.setupPopupImagesTabListeners();
|
||||
this.setupImportExportTabListeners();
|
||||
this.setupAITasksTabListeners();
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.showAnnoyanceTab = function(tabName) {
|
||||
|
|
@ -7411,24 +7396,9 @@ TaskChallengeGame.prototype.showAnnoyanceTab = function(tabName) {
|
|||
|
||||
// Load tab-specific content
|
||||
switch(tabName) {
|
||||
case 'messages':
|
||||
this.loadMessagesTab();
|
||||
break;
|
||||
case 'appearance':
|
||||
this.loadAppearanceTab();
|
||||
break;
|
||||
case 'behavior':
|
||||
this.loadBehaviorTab();
|
||||
break;
|
||||
case 'popup-images':
|
||||
this.loadPopupImagesSettings();
|
||||
break;
|
||||
case 'import-export':
|
||||
this.loadImportExportTab();
|
||||
break;
|
||||
case 'ai-tasks':
|
||||
this.loadAITasksTab();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -8404,280 +8374,7 @@ TaskChallengeGame.prototype.rgbToHex = function(r, g, b) {
|
|||
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// AI Tasks Tab Management
|
||||
// ========================================
|
||||
|
||||
TaskChallengeGame.prototype.setupAITasksTabListeners = function() {
|
||||
// Test Connection Button
|
||||
const testConnectionBtn = document.getElementById('test-connection');
|
||||
if (testConnectionBtn) {
|
||||
testConnectionBtn.onclick = async () => {
|
||||
const btn = testConnectionBtn;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = 'Testing...';
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
if (this.aiTaskManager) {
|
||||
const isConnected = await this.aiTaskManager.testConnection();
|
||||
this.updateConnectionStatus();
|
||||
if (isConnected) {
|
||||
this.flashMessageManager.showMessage('✅ Connected to Ollama successfully!');
|
||||
} else {
|
||||
this.flashMessageManager.showMessage('❌ Cannot connect to Ollama. Check if it\'s running.');
|
||||
}
|
||||
} else {
|
||||
this.flashMessageManager.showMessage('⚠️ AI Manager not initialized');
|
||||
}
|
||||
} catch (error) {
|
||||
this.flashMessageManager.showMessage('❌ Connection test failed: ' + error.message);
|
||||
} finally {
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Generate Test Task Button
|
||||
const generateTestBtn = document.getElementById('generate-test-task');
|
||||
if (generateTestBtn) {
|
||||
generateTestBtn.onclick = async () => {
|
||||
const btn = generateTestBtn;
|
||||
const preview = document.getElementById('test-task-output');
|
||||
const originalText = btn.textContent;
|
||||
|
||||
if (!preview) {
|
||||
console.error('Test task output element not found');
|
||||
this.flashMessageManager.showMessage('❌ UI element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.textContent = 'Generating...';
|
||||
btn.disabled = true;
|
||||
preview.className = 'task-preview generating';
|
||||
preview.textContent = 'AI is creating a personalized edging task...';
|
||||
|
||||
try {
|
||||
if (this.aiTaskManager) {
|
||||
const task = await this.aiTaskManager.generateEdgingTask();
|
||||
preview.className = 'task-preview';
|
||||
preview.textContent = task.instruction || task.description || task;
|
||||
this.flashMessageManager.showMessage('🎯 Test task generated successfully!');
|
||||
} else {
|
||||
throw new Error('AI Manager not initialized');
|
||||
}
|
||||
} catch (error) {
|
||||
preview.className = 'task-preview error';
|
||||
preview.textContent = `Error generating task: ${error.message}`;
|
||||
this.flashMessageManager.showMessage('❌ Failed to generate test task');
|
||||
} finally {
|
||||
btn.textContent = originalText;
|
||||
btn.disabled = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Model Selection Change
|
||||
const modelSelect = document.getElementById('ai-model');
|
||||
if (modelSelect) {
|
||||
modelSelect.onchange = (e) => {
|
||||
const model = e.target.value;
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ model: model });
|
||||
this.flashMessageManager.showMessage(`🔄 Switched to model: ${model}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Temperature Slider
|
||||
const tempSlider = document.getElementById('ai-temperature');
|
||||
const tempValue = document.getElementById('temp-value');
|
||||
if (tempSlider && tempValue) {
|
||||
tempSlider.oninput = (e) => {
|
||||
const temperature = parseFloat(e.target.value);
|
||||
tempValue.textContent = temperature.toFixed(1);
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ temperature: temperature });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Max Tokens Input
|
||||
const maxTokensInput = document.getElementById('ai-max-tokens');
|
||||
if (maxTokensInput) {
|
||||
maxTokensInput.onchange = (e) => {
|
||||
const maxTokens = parseInt(e.target.value);
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ maxTokens: maxTokens });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Difficulty Level Change
|
||||
const difficultySelect = document.getElementById('task-difficulty');
|
||||
if (difficultySelect) {
|
||||
difficultySelect.onchange = (e) => {
|
||||
const difficulty = e.target.value;
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ difficulty: difficulty });
|
||||
this.flashMessageManager.showMessage(`🎯 Difficulty set to: ${difficulty}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Personal Preferences Textarea
|
||||
const prefsTextarea = document.getElementById('personal-preferences');
|
||||
if (prefsTextarea) {
|
||||
prefsTextarea.oninput = this.debounce((e) => {
|
||||
const preferences = e.target.value;
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ personalPreferences: preferences });
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Enable AI Toggle
|
||||
const aiToggle = document.getElementById('enable-ai');
|
||||
if (aiToggle) {
|
||||
aiToggle.onchange = (e) => {
|
||||
const enabled = e.target.checked;
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ enabled: enabled });
|
||||
this.flashMessageManager.showMessage(`🤖 AI Tasks ${enabled ? 'enabled' : 'disabled'}`);
|
||||
|
||||
// Update UI based on toggle
|
||||
const configSection = document.querySelector('.ai-config');
|
||||
if (configSection) {
|
||||
configSection.style.opacity = enabled ? '1' : '0.6';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-generate Toggle
|
||||
const autoGenToggle = document.getElementById('auto-generate');
|
||||
if (autoGenToggle) {
|
||||
autoGenToggle.onchange = (e) => {
|
||||
const autoGenerate = e.target.checked;
|
||||
if (this.aiTaskManager) {
|
||||
this.aiTaskManager.updateSettings({ autoGenerate: autoGenerate });
|
||||
this.flashMessageManager.showMessage(`🔄 Auto-generate ${autoGenerate ? 'enabled' : 'disabled'}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.loadAITasksTab = function() {
|
||||
if (!this.aiTaskManager) return;
|
||||
|
||||
const settings = this.aiTaskManager.getSettings();
|
||||
|
||||
// Load model selection
|
||||
const modelSelect = document.getElementById('ai-model');
|
||||
if (modelSelect) {
|
||||
modelSelect.value = settings.model || 'llama3.2';
|
||||
}
|
||||
|
||||
// Load temperature
|
||||
const tempSlider = document.getElementById('ai-temperature');
|
||||
const tempValue = document.getElementById('temp-value');
|
||||
if (tempSlider && tempValue) {
|
||||
tempSlider.value = settings.temperature || 0.7;
|
||||
tempValue.textContent = (settings.temperature || 0.7).toFixed(1);
|
||||
}
|
||||
|
||||
// Load max tokens
|
||||
const maxTokensInput = document.getElementById('ai-max-tokens');
|
||||
if (maxTokensInput) {
|
||||
maxTokensInput.value = settings.maxTokens || 200;
|
||||
}
|
||||
|
||||
// Load difficulty
|
||||
const difficultySelect = document.getElementById('task-difficulty');
|
||||
if (difficultySelect) {
|
||||
difficultySelect.value = settings.difficulty || 'medium';
|
||||
}
|
||||
|
||||
// Load personal preferences
|
||||
const prefsTextarea = document.getElementById('personal-preferences');
|
||||
if (prefsTextarea) {
|
||||
prefsTextarea.value = settings.personalPreferences || '';
|
||||
}
|
||||
|
||||
// Load toggles
|
||||
const aiToggle = document.getElementById('enable-ai');
|
||||
if (aiToggle) {
|
||||
aiToggle.checked = settings.enabled !== false;
|
||||
}
|
||||
|
||||
const autoGenToggle = document.getElementById('auto-generate');
|
||||
if (autoGenToggle) {
|
||||
autoGenToggle.checked = settings.autoGenerate === true;
|
||||
}
|
||||
|
||||
// Update UI state
|
||||
const configSection = document.querySelector('.ai-config');
|
||||
if (configSection) {
|
||||
configSection.style.opacity = settings.enabled !== false ? '1' : '0.6';
|
||||
}
|
||||
|
||||
// Update connection status (async)
|
||||
this.updateConnectionStatus().catch(error => {
|
||||
console.error('Failed to update connection status:', error);
|
||||
});
|
||||
};
|
||||
|
||||
TaskChallengeGame.prototype.updateConnectionStatus = async function() {
|
||||
const statusValue = document.getElementById('connection-status');
|
||||
const modelStatus = document.getElementById('model-status');
|
||||
|
||||
if (!statusValue || !this.aiTaskManager) {
|
||||
if (statusValue) {
|
||||
statusValue.textContent = 'Not Available';
|
||||
statusValue.className = 'status-value disconnected';
|
||||
}
|
||||
if (modelStatus) {
|
||||
modelStatus.textContent = 'N/A';
|
||||
modelStatus.className = 'status-value';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Set loading state
|
||||
statusValue.textContent = 'Checking...';
|
||||
statusValue.className = 'status-value';
|
||||
|
||||
// Force a fresh connection test
|
||||
const isConnected = await this.aiTaskManager.testConnection();
|
||||
const settings = this.aiTaskManager.getSettings();
|
||||
|
||||
if (isConnected) {
|
||||
statusValue.textContent = 'Connected';
|
||||
statusValue.className = 'status-value connected';
|
||||
if (modelStatus) {
|
||||
modelStatus.textContent = settings.model || this.aiTaskManager.currentModel || 'wizardlm-uncensored:13b';
|
||||
modelStatus.className = 'status-value';
|
||||
}
|
||||
} else {
|
||||
statusValue.textContent = 'Disconnected';
|
||||
statusValue.className = 'status-value disconnected';
|
||||
if (modelStatus) {
|
||||
modelStatus.textContent = 'N/A';
|
||||
modelStatus.className = 'status-value';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating connection status:', error);
|
||||
statusValue.textContent = 'Error';
|
||||
statusValue.className = 'status-value disconnected';
|
||||
if (modelStatus) {
|
||||
modelStatus.textContent = 'N/A';
|
||||
modelStatus.className = 'status-value';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Utility function for debouncing input
|
||||
TaskChallengeGame.prototype.debounce = function(func, wait) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class PopupImageManager {
|
|||
maxInterval: 120000, // 2 minutes
|
||||
displayDuration: 5000, // 5 seconds
|
||||
history: [],
|
||||
maxHistorySize: 20
|
||||
maxHistorySize: 50 // Increased from 20 to allow more variety
|
||||
};
|
||||
|
||||
this.init();
|
||||
|
|
@ -68,8 +68,26 @@ class PopupImageManager {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('🔍 Current periodicSystem state:', this.periodicSystem);
|
||||
const { minInterval, maxInterval } = this.periodicSystem;
|
||||
const interval = Math.random() * (maxInterval - minInterval) + minInterval;
|
||||
|
||||
// Validate intervals before calculation
|
||||
if (typeof minInterval !== 'number' || typeof maxInterval !== 'number' ||
|
||||
isNaN(minInterval) || isNaN(maxInterval) ||
|
||||
minInterval <= 0 || maxInterval <= 0) {
|
||||
console.error('❌ Invalid periodic popup intervals:', {
|
||||
minInterval,
|
||||
maxInterval,
|
||||
minType: typeof minInterval,
|
||||
maxType: typeof maxInterval
|
||||
});
|
||||
console.log('🔄 Resetting to default intervals');
|
||||
this.periodicSystem.minInterval = 30000; // 30 seconds
|
||||
this.periodicSystem.maxInterval = 120000; // 2 minutes
|
||||
return this.scheduleNextPeriodicPopup();
|
||||
}
|
||||
|
||||
const interval = minInterval > maxInterval ? minInterval : Math.random() * (maxInterval - minInterval) + minInterval;
|
||||
|
||||
this.periodicSystem.interval = setTimeout(async () => {
|
||||
try {
|
||||
|
|
@ -91,13 +109,106 @@ class PopupImageManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const image = await this.getRandomPeriodicImage();
|
||||
if (!image) {
|
||||
// Get the number of images to show based on configuration
|
||||
const imageCount = this.getImageCount();
|
||||
console.log(`🖼️ Showing ${imageCount} periodic popup images`);
|
||||
|
||||
const images = await this.getMultipleRandomPeriodicImages(imageCount);
|
||||
if (images.length === 0) {
|
||||
console.log('⚠️ No images available for periodic popup');
|
||||
return;
|
||||
}
|
||||
|
||||
this.displayPeriodicPopup(image);
|
||||
this.displayMultiplePeriodicPopups(images);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of images to show based on configuration
|
||||
*/
|
||||
getImageCount() {
|
||||
const config = this.config || {};
|
||||
|
||||
if (config.countMode === 'fixed') {
|
||||
return Math.max(1, config.imageCount || 1);
|
||||
} else if (config.countMode === 'range') {
|
||||
const min = Math.max(1, config.minCount || 1);
|
||||
const max = Math.max(min, config.maxCount || 3);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
// Default to 1 if no configuration
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get multiple random images for periodic popups
|
||||
*/
|
||||
async getMultipleRandomPeriodicImages(count) {
|
||||
const allImages = await this.getLinkedImages();
|
||||
if (allImages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out disabled images and recent history
|
||||
const disabledImages = this.dataManager.get('disabledImages') || [];
|
||||
const { history } = this.periodicSystem;
|
||||
|
||||
// Dynamically adjust history size based on available images
|
||||
// Keep history to about 30% of total images, minimum 20, maximum 100
|
||||
const totalImages = allImages.length;
|
||||
const optimalHistorySize = Math.min(100, Math.max(20, Math.floor(totalImages * 0.3)));
|
||||
if (this.periodicSystem.maxHistorySize !== optimalHistorySize) {
|
||||
this.periodicSystem.maxHistorySize = optimalHistorySize;
|
||||
console.log(`📊 Adjusted history size to ${optimalHistorySize} (30% of ${totalImages} total images)`);
|
||||
}
|
||||
|
||||
const availableImages = allImages.filter(img => {
|
||||
const imagePath = typeof img === 'string' ? img : img.src;
|
||||
return !disabledImages.includes(imagePath) && !history.includes(imagePath);
|
||||
});
|
||||
|
||||
if (availableImages.length === 0) {
|
||||
console.log('⚠️ No available images (all disabled or recently shown)');
|
||||
console.log('🔄 Clearing history to allow image reuse');
|
||||
this.periodicSystem.history = [];
|
||||
|
||||
// Retry with cleared history
|
||||
const retryAvailableImages = allImages.filter(img => {
|
||||
const imagePath = typeof img === 'string' ? img : img.src;
|
||||
return !disabledImages.includes(imagePath);
|
||||
});
|
||||
|
||||
if (retryAvailableImages.length === 0) {
|
||||
console.log('❌ No images available even after clearing history');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Use the retry images
|
||||
availableImages.length = 0;
|
||||
availableImages.push(...retryAvailableImages);
|
||||
console.log(`✅ Found ${availableImages.length} images after clearing history`);
|
||||
}
|
||||
|
||||
// Get unique random images
|
||||
const selectedImages = [];
|
||||
const imagesToChooseFrom = [...availableImages];
|
||||
|
||||
for (let i = 0; i < Math.min(count, imagesToChooseFrom.length); i++) {
|
||||
const randomIndex = Math.floor(Math.random() * imagesToChooseFrom.length);
|
||||
const selectedImage = imagesToChooseFrom.splice(randomIndex, 1)[0];
|
||||
selectedImages.push(selectedImage);
|
||||
|
||||
// Add to history
|
||||
const imagePath = typeof selectedImage === 'string' ? selectedImage : selectedImage.src;
|
||||
this.periodicSystem.history.push(imagePath);
|
||||
|
||||
// Manage history size
|
||||
if (this.periodicSystem.history.length > this.periodicSystem.maxHistorySize) {
|
||||
this.periodicSystem.history.shift();
|
||||
}
|
||||
}
|
||||
|
||||
return selectedImages;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,7 +353,211 @@ class PopupImageManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Display periodic popup
|
||||
* Display multiple periodic popups with positioning
|
||||
*/
|
||||
displayMultiplePeriodicPopups(images) {
|
||||
const config = this.config || {};
|
||||
const positioning = config.positioning || 'center';
|
||||
const allowOverlap = config.allowOverlap !== false;
|
||||
|
||||
console.log(`🎯 Positioning ${images.length} images using '${positioning}' mode`);
|
||||
|
||||
images.forEach((imageData, index) => {
|
||||
const position = this.calculatePopupPosition(positioning, index, images.length, allowOverlap);
|
||||
this.displayPositionedPeriodicPopup(imageData, position, index);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position for a popup based on positioning mode
|
||||
*/
|
||||
calculatePopupPosition(positioning, index, total, allowOverlap) {
|
||||
const config = this.config || {};
|
||||
const viewportWidth = config.viewportWidth || window.innerWidth;
|
||||
const viewportHeight = config.viewportHeight || window.innerHeight;
|
||||
|
||||
// Popup dimensions (approximate)
|
||||
const popupWidth = 400;
|
||||
const popupHeight = 300;
|
||||
|
||||
let position = { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };
|
||||
|
||||
switch (positioning) {
|
||||
case 'random':
|
||||
if (!allowOverlap && total > 1) {
|
||||
// Grid distribution with some randomness for non-overlapping positions
|
||||
const cols = Math.ceil(Math.sqrt(total));
|
||||
const rows = Math.ceil(total / cols);
|
||||
const col = index % cols;
|
||||
const row = Math.floor(index / cols);
|
||||
|
||||
// Calculate available space
|
||||
const maxLeft = Math.max(100, viewportWidth - popupWidth - 50);
|
||||
const maxTop = Math.max(100, viewportHeight - popupHeight - 50);
|
||||
|
||||
const sectionWidth = maxLeft / cols;
|
||||
const sectionHeight = maxTop / rows;
|
||||
|
||||
// Add some randomness within each section
|
||||
const randomOffsetX = Math.random() * Math.min(100, sectionWidth * 0.3);
|
||||
const randomOffsetY = Math.random() * Math.min(100, sectionHeight * 0.3);
|
||||
|
||||
const left = Math.max(50, (col * sectionWidth) + randomOffsetX);
|
||||
const top = Math.max(50, (row * sectionHeight) + randomOffsetY);
|
||||
|
||||
position = {
|
||||
top: `${top}px`,
|
||||
left: `${left}px`,
|
||||
transform: 'none'
|
||||
};
|
||||
} else {
|
||||
// Completely random positioning (overlapping allowed)
|
||||
const maxLeft = Math.max(50, viewportWidth - popupWidth);
|
||||
const maxTop = Math.max(50, viewportHeight - popupHeight);
|
||||
|
||||
const randomLeft = 50 + Math.random() * Math.max(0, maxLeft - 50);
|
||||
const randomTop = 50 + Math.random() * Math.max(0, maxTop - 50);
|
||||
|
||||
position = {
|
||||
top: `${randomTop}px`,
|
||||
left: `${randomLeft}px`,
|
||||
transform: 'none'
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case 'grid':
|
||||
const cols = Math.ceil(Math.sqrt(total));
|
||||
const rows = Math.ceil(total / cols);
|
||||
const col = index % cols;
|
||||
const row = Math.floor(index / cols);
|
||||
|
||||
const gridLeft = (viewportWidth / cols) * col + (viewportWidth / cols - popupWidth) / 2;
|
||||
const gridTop = (viewportHeight / rows) * row + (viewportHeight / rows - popupHeight) / 2;
|
||||
|
||||
position = {
|
||||
top: `${Math.max(0, gridTop)}px`,
|
||||
left: `${Math.max(0, gridLeft)}px`,
|
||||
transform: 'none'
|
||||
};
|
||||
break;
|
||||
|
||||
case 'corners':
|
||||
const corners = [
|
||||
{ top: '10%', left: '10%' },
|
||||
{ top: '10%', right: '10%' },
|
||||
{ bottom: '10%', left: '10%' },
|
||||
{ bottom: '10%', right: '10%' }
|
||||
];
|
||||
position = corners[index % corners.length] || corners[0];
|
||||
break;
|
||||
|
||||
case 'center':
|
||||
default:
|
||||
// Center with offset for multiple images
|
||||
if (total > 1) {
|
||||
if (!allowOverlap) {
|
||||
// Spread them out more when overlapping is disabled
|
||||
const spacing = Math.min(200, (viewportWidth - popupWidth) / total);
|
||||
const offset = (index - (total - 1) / 2) * spacing;
|
||||
position = {
|
||||
top: '50%',
|
||||
left: `calc(50% + ${offset}px)`,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
};
|
||||
} else {
|
||||
// Smaller offset when overlapping is allowed
|
||||
const offset = (index - (total - 1) / 2) * 50;
|
||||
position = {
|
||||
top: '50%',
|
||||
left: `calc(50% + ${offset}px)`,
|
||||
transform: 'translate(-50%, -50%)'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Single image, center it perfectly
|
||||
position = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a positioned periodic popup
|
||||
*/
|
||||
displayPositionedPeriodicPopup(imageData, position, index) {
|
||||
const imageSrc = this.getImageSrc(imageData);
|
||||
const imageName = typeof imageData === 'string' ? 'Random Image' : (imageData.originalName || 'Random Image');
|
||||
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'periodic-popup-positioned';
|
||||
// Apply positioning with !important to override any CSS conflicts
|
||||
popup.style.cssText = `
|
||||
position: fixed !important;
|
||||
z-index: ${10000 + index} !important;
|
||||
top: ${position.top} !important;
|
||||
left: ${position.left} !important;
|
||||
${position.transform ? `transform: ${position.transform} !important;` : ''}
|
||||
`;
|
||||
|
||||
console.log(`🎯 Applied position for popup ${index + 1}:`, position);
|
||||
|
||||
popup.innerHTML = `
|
||||
<div class="periodic-popup-content" onclick="event.stopPropagation()">
|
||||
<div class="periodic-popup-header">
|
||||
<span class="periodic-popup-title">💫 Random Visual ${index + 1}</span>
|
||||
<button class="periodic-popup-close" onclick="window.popupImageManager.hidePeriodicPopup(this.closest('.periodic-popup-positioned'))">×</button>
|
||||
</div>
|
||||
<div class="periodic-popup-image-wrapper">
|
||||
<img src="${imageSrc}" alt="Random Popup" class="periodic-popup-image">
|
||||
</div>
|
||||
<div class="periodic-popup-footer">
|
||||
<small>${imageName}</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.ensurePeriodicStyles();
|
||||
document.body.appendChild(popup);
|
||||
|
||||
// Animate in with slight delay for each popup
|
||||
setTimeout(() => {
|
||||
popup.classList.add('periodic-visible');
|
||||
}, 50 + (index * 100));
|
||||
|
||||
// Auto-hide after configured duration
|
||||
setTimeout(() => {
|
||||
this.hidePeriodicPopup(popup);
|
||||
}, this.getDisplayDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display duration based on configuration
|
||||
*/
|
||||
getDisplayDuration() {
|
||||
const config = this.config || {};
|
||||
|
||||
if (config.durationMode === 'fixed') {
|
||||
return (config.displayDuration || 5) * 1000;
|
||||
} else if (config.durationMode === 'range') {
|
||||
const min = Math.max(1, config.minDuration || 3);
|
||||
const max = Math.max(min, config.maxDuration || 10);
|
||||
const randomDuration = Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
return randomDuration * 1000;
|
||||
}
|
||||
|
||||
// Default to 5 seconds
|
||||
return 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display periodic popup (legacy single popup method)
|
||||
*/
|
||||
displayPeriodicPopup(imageData) {
|
||||
const imageSrc = this.getImageSrc(imageData);
|
||||
|
|
@ -311,14 +626,15 @@ class PopupImageManager {
|
|||
styles.textContent = `
|
||||
.periodic-popup-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10001;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.periodic-popup-container.periodic-visible {
|
||||
|
|
@ -332,13 +648,31 @@ class PopupImageManager {
|
|||
}
|
||||
|
||||
.periodic-popup-backdrop {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.periodic-popup-positioned {
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
max-width: 400px;
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.periodic-popup-positioned.periodic-hiding {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.periodic-popup-content {
|
||||
|
|
@ -438,9 +772,22 @@ class PopupImageManager {
|
|||
* Update periodic popup settings
|
||||
*/
|
||||
updatePeriodicSettings(settings) {
|
||||
if (settings.minInterval) this.periodicSystem.minInterval = settings.minInterval * 1000;
|
||||
if (settings.maxInterval) this.periodicSystem.maxInterval = settings.maxInterval * 1000;
|
||||
if (settings.displayDuration) this.periodicSystem.displayDuration = settings.displayDuration * 1000;
|
||||
console.log('🔧 Received settings:', settings);
|
||||
|
||||
if (typeof settings.minInterval === 'number' && !isNaN(settings.minInterval) && settings.minInterval > 0) {
|
||||
this.periodicSystem.minInterval = settings.minInterval * 1000;
|
||||
}
|
||||
if (typeof settings.maxInterval === 'number' && !isNaN(settings.maxInterval) && settings.maxInterval > 0) {
|
||||
this.periodicSystem.maxInterval = settings.maxInterval * 1000;
|
||||
}
|
||||
if (typeof settings.displayDuration === 'number' && !isNaN(settings.displayDuration) && settings.displayDuration > 0) {
|
||||
this.periodicSystem.displayDuration = settings.displayDuration * 1000;
|
||||
}
|
||||
|
||||
// Ensure minInterval is not greater than maxInterval
|
||||
if (this.periodicSystem.minInterval > this.periodicSystem.maxInterval) {
|
||||
this.periodicSystem.minInterval = this.periodicSystem.maxInterval;
|
||||
}
|
||||
|
||||
console.log('🔧 Periodic popup settings updated:', {
|
||||
minInterval: this.periodicSystem.minInterval / 1000 + 's',
|
||||
|
|
@ -481,14 +828,29 @@ class PopupImageManager {
|
|||
|
||||
// Update periodic system timing if frequency/duration config is provided
|
||||
if (newConfig.frequency) {
|
||||
this.periodicSystem.minInterval = newConfig.frequency.min;
|
||||
this.periodicSystem.maxInterval = newConfig.frequency.max;
|
||||
console.log('Updated periodic system frequency:', newConfig.frequency);
|
||||
console.log('🔧 Updating periodic system from config frequency:', newConfig.frequency);
|
||||
|
||||
if (typeof newConfig.frequency.min === 'number' && newConfig.frequency.min > 0) {
|
||||
this.periodicSystem.minInterval = newConfig.frequency.min * 1000; // Convert to milliseconds
|
||||
}
|
||||
if (typeof newConfig.frequency.max === 'number' && newConfig.frequency.max > 0) {
|
||||
this.periodicSystem.maxInterval = newConfig.frequency.max * 1000; // Convert to milliseconds
|
||||
}
|
||||
|
||||
console.log('Updated periodic system frequency:', {
|
||||
min: this.periodicSystem.minInterval / 1000 + 's',
|
||||
max: this.periodicSystem.maxInterval / 1000 + 's'
|
||||
});
|
||||
}
|
||||
|
||||
if (newConfig.duration) {
|
||||
this.periodicSystem.displayDuration = newConfig.duration.min; // Use min duration for consistency
|
||||
console.log('Updated periodic system duration:', newConfig.duration);
|
||||
console.log('🔧 Updating periodic system from config duration:', newConfig.duration);
|
||||
|
||||
if (typeof newConfig.duration.min === 'number' && newConfig.duration.min > 0) {
|
||||
this.periodicSystem.displayDuration = newConfig.duration.min * 1000; // Convert to milliseconds
|
||||
}
|
||||
|
||||
console.log('Updated periodic system duration:', this.periodicSystem.displayDuration / 1000 + 's');
|
||||
}
|
||||
|
||||
console.log('Popup image config updated:', newConfig);
|
||||
|
|
|
|||
|
|
@ -40,19 +40,6 @@ class VideoLibrary {
|
|||
// Content container
|
||||
this.libraryContent = document.getElementById('library-content');
|
||||
|
||||
// Debug: Log which elements were found
|
||||
console.log(`📁 VideoLibrary elements initialized:`, {
|
||||
gridViewBtn: !!this.gridViewBtn,
|
||||
listViewBtn: !!this.listViewBtn,
|
||||
sortSelect: !!this.sortSelect,
|
||||
sortDirectionBtn: !!this.sortDirectionBtn,
|
||||
searchInput: !!this.searchInput,
|
||||
refreshBtn: !!this.refreshBtn,
|
||||
createPlaylistBtn: !!this.createPlaylistBtn,
|
||||
selectModeBtn: !!this.selectModeBtn,
|
||||
libraryContent: !!this.libraryContent
|
||||
});
|
||||
|
||||
if (!this.libraryContent) {
|
||||
console.error(`📁 ❌ library-content element not found!`);
|
||||
}
|
||||
|
|
@ -258,16 +245,7 @@ class VideoLibrary {
|
|||
}
|
||||
}
|
||||
|
||||
// Debug: Show some sample video names and paths
|
||||
if (this.videos.length > 0) {
|
||||
console.log(`📁 Sample videos loaded:`);
|
||||
this.videos.slice(0, 5).forEach((video, index) => {
|
||||
console.log(` ${index + 1}. "${video.name}" (${video.category}) - ${video.path}`);
|
||||
});
|
||||
if (this.videos.length > 5) {
|
||||
console.log(` ... and ${this.videos.length - 5} more videos`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Apply current filters and display
|
||||
this.applyFiltersAndSort();
|
||||
|
|
@ -399,7 +377,7 @@ class VideoLibrary {
|
|||
}
|
||||
|
||||
createVideoElement(video) {
|
||||
console.log('🔍 DEBUG: createVideoElement called for:', video.name);
|
||||
|
||||
const duration = this.formatDuration(video.duration);
|
||||
const fileSize = this.formatFileSize(video.size);
|
||||
const isSelected = this.selectedVideos.has(video.path);
|
||||
|
|
@ -410,7 +388,7 @@ class VideoLibrary {
|
|||
|
||||
if (this.currentView === 'grid') {
|
||||
return `
|
||||
<div class="video-card ${selectionClass}" data-video-path="${video.path}" style="background: red !important; border: 3px solid lime !important; height: auto !important; padding: 0 !important; margin: 0 !important; min-height: unset !important; overflow: hidden; border-radius: 10px;">
|
||||
<div class="video-card ${selectionClass}" data-video-path="${video.path}">
|
||||
${this.isSelectMode ? `<div class="video-selection-checkbox ${isSelected ? 'checked' : ''}">✓</div>` : ''}
|
||||
<div class="video-thumbnail" data-video-path="${video.path}" style="height: 80px !important; width: 100% !important; position: relative; overflow: hidden;">
|
||||
${thumbnailSrc ?
|
||||
|
|
|
|||
|
|
@ -1,365 +0,0 @@
|
|||
/**
|
||||
* AI Task Manager - Ollama Integration for Gooner Training Academy
|
||||
* Generates NSFW edging tasks using local AI models
|
||||
*/
|
||||
class AITaskManager {
|
||||
constructor(dataManager) {
|
||||
this.dataManager = dataManager;
|
||||
this.ollamaUrl = 'http://localhost:11434';
|
||||
this.isAvailable = false;
|
||||
this.availableModels = [];
|
||||
this.currentModel = 'wizardlm-uncensored:13b'; // Default NSFW-friendly model
|
||||
this.isGenerating = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.log('AITaskManager initializing...');
|
||||
console.log('Testing basic fetch capability...');
|
||||
|
||||
// Test if fetch works at all
|
||||
try {
|
||||
const testResponse = await fetch('http://localhost:11434/api/tags');
|
||||
console.log('Basic fetch test result:', testResponse.status);
|
||||
} catch (testError) {
|
||||
console.error('Basic fetch test failed:', testError);
|
||||
}
|
||||
|
||||
await this.checkAvailability();
|
||||
await this.loadModels();
|
||||
this.loadConfig();
|
||||
console.log('AITaskManager initialized:', this.isAvailable ? 'Ollama available' : 'Ollama not available');
|
||||
}
|
||||
|
||||
async checkAvailability() {
|
||||
try {
|
||||
console.log('Testing Ollama connection to:', this.ollamaUrl);
|
||||
|
||||
// Create a manual timeout controller for better browser compatibility
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
||||
|
||||
const response = await fetch(`${this.ollamaUrl}/api/tags`, {
|
||||
method: 'GET',
|
||||
signal: controller.signal,
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
console.log('Response status:', response.status);
|
||||
console.log('Response ok:', response.ok);
|
||||
|
||||
this.isAvailable = response.ok;
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('Available models from API:', data.models?.map(m => m.name));
|
||||
}
|
||||
|
||||
console.log('Ollama connection test result:', this.isAvailable ? 'SUCCESS' : 'FAILED');
|
||||
return this.isAvailable;
|
||||
} catch (error) {
|
||||
console.error('Ollama connection error details:', error);
|
||||
console.log('Error name:', error.name);
|
||||
console.log('Error message:', error.message);
|
||||
this.isAvailable = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async loadModels() {
|
||||
if (!this.isAvailable) {
|
||||
console.log('Skipping model loading - Ollama not available');
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Loading models from Ollama...');
|
||||
const response = await fetch(`${this.ollamaUrl}/api/tags`, {
|
||||
method: 'GET',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
this.availableModels = data.models || [];
|
||||
|
||||
console.log('Loaded models:', this.availableModels.map(m => m.name));
|
||||
|
||||
// Check if our preferred NSFW models are available
|
||||
const modelNames = this.availableModels.map(m => m.name);
|
||||
const preferredModels = ['wizardlm-uncensored:13b', 'llama3.1:8b-instruct', 'dolphin-mistral:7b', 'wizardlm-uncensored:7b', 'llama3.2'];
|
||||
|
||||
console.log('Checking preferred models:', preferredModels);
|
||||
console.log('Available model names:', modelNames);
|
||||
|
||||
for (const preferred of preferredModels) {
|
||||
if (modelNames.includes(preferred)) {
|
||||
console.log('Found preferred model:', preferred);
|
||||
this.currentModel = preferred;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no preferred models found, use the first available model
|
||||
if (this.availableModels.length > 0 && !modelNames.includes(this.currentModel)) {
|
||||
this.currentModel = this.availableModels[0].name;
|
||||
console.log('Using first available model:', this.currentModel);
|
||||
}
|
||||
|
||||
console.log('Selected model:', this.currentModel);
|
||||
return this.availableModels;
|
||||
} catch (error) {
|
||||
console.error('Error loading models:', error);
|
||||
this.availableModels = [];
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
loadConfig() {
|
||||
const savedConfig = this.dataManager.get('aiTaskConfig');
|
||||
this.config = {
|
||||
enabled: false,
|
||||
model: this.currentModel,
|
||||
temperature: 0.8,
|
||||
maxTokens: 300,
|
||||
userPreferences: {
|
||||
experience: 'intermediate', // beginner, intermediate, advanced
|
||||
intensity: 'medium', // low, medium, high, extreme
|
||||
duration: 5, // minutes
|
||||
style: 'instructional', // instructional, descriptive, commanding
|
||||
kinks: [], // user-selected interests
|
||||
limits: [] // user-defined hard limits
|
||||
},
|
||||
...savedConfig
|
||||
};
|
||||
}
|
||||
|
||||
updateConfig(newConfig) {
|
||||
this.config = { ...this.config, ...newConfig };
|
||||
this.dataManager.set('aiTaskConfig', this.config);
|
||||
|
||||
if (newConfig.model) {
|
||||
this.currentModel = newConfig.model;
|
||||
}
|
||||
}
|
||||
|
||||
// Alias method for UI compatibility
|
||||
updateSettings(newSettings) {
|
||||
return this.updateConfig(newSettings);
|
||||
}
|
||||
|
||||
// Alias method for UI compatibility
|
||||
getSettings() {
|
||||
return this.getConfig();
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
async generateEdgingTask(customPrefs = {}) {
|
||||
if (!this.isAvailable) {
|
||||
throw new Error('AI not available. Please ensure Ollama is running and models are installed.');
|
||||
}
|
||||
|
||||
if (this.isGenerating) {
|
||||
throw new Error('Already generating a task. Please wait...');
|
||||
}
|
||||
|
||||
this.isGenerating = true;
|
||||
|
||||
try {
|
||||
const prefs = { ...this.config.userPreferences, ...customPrefs };
|
||||
const prompt = this.buildEdgingPrompt(prefs);
|
||||
|
||||
console.log('Generating AI task with model:', this.currentModel);
|
||||
|
||||
const response = await fetch(`${this.ollamaUrl}/api/generate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: this.currentModel,
|
||||
prompt: prompt,
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: this.config.temperature,
|
||||
num_predict: this.config.maxTokens,
|
||||
stop: ['User:', 'Human:', 'Assistant:']
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Ollama API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const taskText = data.response.trim();
|
||||
|
||||
if (!taskText || taskText.length < 20) {
|
||||
throw new Error('Generated task was too short or empty');
|
||||
}
|
||||
|
||||
// Create task object compatible with existing game system
|
||||
const aiTask = {
|
||||
id: `ai-task-${Date.now()}`,
|
||||
type: 'ai-generated',
|
||||
category: 'edging',
|
||||
instruction: taskText,
|
||||
duration: prefs.duration * 60000, // Convert to milliseconds
|
||||
difficulty: prefs.intensity,
|
||||
source: 'ollama',
|
||||
model: this.currentModel,
|
||||
generated: new Date().toISOString(),
|
||||
preferences: prefs
|
||||
};
|
||||
|
||||
console.log('AI Task Generated:', aiTask.instruction.substring(0, 100) + '...');
|
||||
return aiTask;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generating AI task:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.isGenerating = false;
|
||||
}
|
||||
}
|
||||
|
||||
buildEdgingPrompt(prefs) {
|
||||
const basePrompt = `You are an expert in creating edging challenges. Generate a detailed ${prefs.duration}-minute edging task with ${prefs.intensity} intensity for someone with ${prefs.experience} experience.
|
||||
|
||||
The task should include:
|
||||
- Clear step-by-step instructions
|
||||
- Specific timing and rhythm guidance
|
||||
- Techniques for building arousal without climax
|
||||
- Commands for start, stop, and pause moments
|
||||
- Breathing and focus instructions
|
||||
- Progressive intensity building
|
||||
|
||||
Style: ${prefs.style}
|
||||
Duration: ${prefs.duration} minutes exactly
|
||||
Intensity: ${prefs.intensity}
|
||||
Experience Level: ${prefs.experience}
|
||||
|
||||
Generate only the task instructions, no introduction or explanation:
|
||||
|
||||
Task Instructions:`;
|
||||
|
||||
return basePrompt;
|
||||
}
|
||||
|
||||
async generateConsequenceTask(skippedTask, severity = 'medium') {
|
||||
if (!this.isAvailable) return null;
|
||||
|
||||
const prompt = `Create a consequence task for someone who skipped an edging challenge. This should be a punishment task with ${severity} severity that teaches discipline and makes them regret skipping.
|
||||
|
||||
The consequence should:
|
||||
- Be more challenging than the original task
|
||||
- Include elements of denial or frustration
|
||||
- Have a longer duration (at least 7-10 minutes)
|
||||
- Include specific punishments or restrictions
|
||||
- Make the user understand the cost of skipping
|
||||
|
||||
Generate only the consequence task instructions:
|
||||
|
||||
Consequence Task:`;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.ollamaUrl}/api/generate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: this.currentModel,
|
||||
prompt: prompt,
|
||||
stream: false,
|
||||
options: {
|
||||
temperature: 0.9,
|
||||
num_predict: this.config.maxTokens
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
id: `ai-consequence-${Date.now()}`,
|
||||
type: 'ai-consequence',
|
||||
instruction: data.response.trim(),
|
||||
duration: 600000, // 10 minutes
|
||||
difficulty: 'punishment',
|
||||
source: 'ollama',
|
||||
isConsequence: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error generating consequence task:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async testConnection() {
|
||||
console.log('Starting connection test...');
|
||||
const available = await this.checkAvailability();
|
||||
|
||||
if (!available) {
|
||||
console.log('Connection test failed: Ollama service not available');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Ollama available, loading models...');
|
||||
await this.loadModels();
|
||||
|
||||
if (this.availableModels.length === 0) {
|
||||
console.log('Connection test failed: No models installed');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log(`Found ${this.availableModels.length} models, using: ${this.currentModel}`);
|
||||
|
||||
try {
|
||||
// Quick test generation to verify the model works
|
||||
console.log('Testing model with simple task generation...');
|
||||
const testTask = await this.generateEdgingTask({
|
||||
duration: 1,
|
||||
intensity: 'low',
|
||||
experience: 'beginner'
|
||||
});
|
||||
|
||||
console.log('Connection test successful! Generated test task.');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Connection test failed during task generation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
getAvailableModels() {
|
||||
return this.availableModels;
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return this.isAvailable && this.availableModels.length > 0;
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return {
|
||||
available: this.isAvailable,
|
||||
generating: this.isGenerating,
|
||||
model: this.currentModel,
|
||||
modelCount: this.availableModels.length,
|
||||
config: this.config
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1685,6 +1685,135 @@ body.cinema-mode {
|
|||
opacity: 1;
|
||||
}
|
||||
|
||||
/* ===== THEATER MODE ACTIVE ===== */
|
||||
body.theater-mode-active {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.theater-mode-active .cinema-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2000;
|
||||
background: rgba(42, 42, 58, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
body.theater-mode-active .cinema-header:hover,
|
||||
body.theater-mode-active .cinema-header.show-on-hover {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
body.theater-mode-active .cinema-main {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.theater-mode-active .main-content-area {
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
body.theater-mode-active .video-player-section {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
body.theater-mode-active .video-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
body.theater-mode-active .main-video {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Auto-hide header in theater mode */
|
||||
body.theater-mode-active .cinema-header.auto-hide {
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
transition-delay: 3s;
|
||||
}
|
||||
|
||||
/* Show header on mouse movement near top */
|
||||
body.theater-mode-active .header-hover-zone {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 80px;
|
||||
z-index: 1999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Theater mode controls styling */
|
||||
body.theater-mode-active .video-controls {
|
||||
background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.9));
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* Theater mode escape hint */
|
||||
.theater-escape-hint {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
z-index: 2001;
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 107, 157, 0.3);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.theater-escape-hint kbd {
|
||||
background: rgba(255, 107, 157, 0.2);
|
||||
border: 1px solid rgba(255, 107, 157, 0.4);
|
||||
border-radius: 4px;
|
||||
padding: 2px 6px;
|
||||
font-size: 0.8rem;
|
||||
margin: 0 2px;
|
||||
color: #ff6b9d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
body.theater-mode-active .theater-escape-hint {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
body.theater-mode-active .theater-escape-hint.fade-out {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transition-delay: 3s;
|
||||
}
|
||||
|
||||
/* ===== FULLSCREEN STYLES ===== */
|
||||
.video-container:-webkit-full-screen {
|
||||
width: 100vw;
|
||||
|
|
|
|||
|
|
@ -4654,157 +4654,7 @@ input[type="number"]:focus, input[type="text"]:focus, select:focus {
|
|||
animation: popupFadeOut 0.3s ease-in;
|
||||
}
|
||||
|
||||
/* ======================================
|
||||
AI Tasks Tab Styles
|
||||
====================================== */
|
||||
|
||||
.ai-status-display {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
border-left: 4px solid #17a2b8;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.status-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-weight: bold;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
background: rgba(23, 162, 184, 0.1);
|
||||
color: #17a2b8;
|
||||
}
|
||||
|
||||
.status-value.connected {
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.status-value.disconnected {
|
||||
background: rgba(220, 53, 69, 0.1);
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.ai-test-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.ai-test-buttons .btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.task-preview {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
min-height: 100px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
white-space: pre-wrap;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.task-preview.generating {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-preview.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border-color: #f5c6cb;
|
||||
}
|
||||
|
||||
.help-content {
|
||||
background: #e7f3ff;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #007bff;
|
||||
}
|
||||
|
||||
.help-content ol {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.help-content ul {
|
||||
margin: 5px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.help-content code {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.help-content a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.help-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive styles for AI tab */
|
||||
@media (max-width: 768px) {
|
||||
.ai-test-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ai-test-buttons .btn {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* Audio Controls Styling */
|
||||
.audio-controls {
|
||||
|
|
|
|||
|
|
@ -964,7 +964,7 @@
|
|||
<script src="src/features/webcam/webcamManager.js"></script>
|
||||
<script src="src/features/stats/playerStats.js"></script>
|
||||
<script src="src/features/ui/flashMessageManager.js"></script>
|
||||
<script src="src/features/tasks/aiTaskManager.js"></script>
|
||||
|
||||
<script src="src/features/tasks/interactiveTaskManager.js"></script>
|
||||
|
||||
<!-- Data Scripts -->
|
||||
|
|
|
|||
Loading…
Reference in New Issue