Files
Trackeep/frontend/src/lib/demo-mode.ts
T
Tomas Dvorak 954a1a1080 feat: migrate to DragonflyDB and clean up environment configuration
- Replace Redis with DragonflyDB for better performance and memory efficiency
- Remove redundant environment variables (POSTGRES_*, ENCRYPTION_KEY, OAUTH_SERVICE_URL)
- Consolidate database configuration to use single DB_* variables
- Use JWT_SECRET for both JWT tokens and encryption
- Remove PORT variable redundancy, use BACKEND_PORT consistently
- Clean up docker-compose configurations for dev/prod consistency
- Add DragonflyDB configuration with optimized memory usage
- Remove redis.conf as it's no longer needed
- Update health checks to use Redis-compatible CLI for DragonflyDB
- Add missing VITE_API_URL to production frontend
- Fix GitHub Actions to use correct go.sum path
- Clean up development directories and unused files
2026-03-05 23:51:34 +01:00

1939 lines
61 KiB
TypeScript

// Demo mode API interceptor to provide mock data instead of making real API calls
import { hasAnyCredentials, isBackendAvailable, isSearchAvailable } from './credentials';
// Check if demo mode is enabled via environment variable
export const isEnvDemoMode = (): boolean => {
// First check runtime environment variable (for Docker) - this should take priority
const runtimeResult = (window as any).ENV?.VITE_DEMO_MODE === 'true';
// Check window.importMetaEnv as well
const importMetaEnvResult = (window as any).importMetaEnv?.VITE_DEMO_MODE === 'true';
// Then check build-time environment variable as fallback
const buildTimeResult = import.meta.env.VITE_DEMO_MODE === 'true';
const result = runtimeResult || importMetaEnvResult || buildTimeResult;
return result;
};
// Check if demo mode is active (environment variable only)
export const isDemoMode = (): boolean => {
// Only check environment variable - no localStorage persistence
return isEnvDemoMode();
};
// Check if we should use real APIs even in demo mode
export const shouldUseRealAPIs = (): boolean => {
const hasCredentials = hasAnyCredentials();
const hasBackend = shouldUseRealBackend();
const result = hasCredentials || hasBackend;
return result;
};
// Check if we should use real backend API
export const shouldUseRealBackend = (): boolean => {
return isBackendAvailable();
};
// Check if we should use real search APIs
export const shouldUseRealSearch = (): boolean => {
return isSearchAvailable();
};
// Clear demo mode from localStorage
export const clearDemoMode = (): void => {
localStorage.removeItem('demoMode');
// Only clear demo tokens, not legitimate user tokens
const token = localStorage.getItem('token');
// Only clear if they look like demo tokens (contain 'demo-token')
if (token && token.includes('demo-token')) {
localStorage.removeItem('token');
localStorage.removeItem('user');
localStorage.removeItem('trackeep_token');
localStorage.removeItem('trackeep_user');
}
};
// Set demo mode (no-op - environment variable only)
export const setDemoMode = (): void => {
// Demo mode is controlled by environment variable only
// No localStorage persistence
};
// Mock data generators
const generateMockStats = () => ({
total_tasks: 42,
completed_tasks: 28,
total_bookmarks: 156,
total_notes: 89,
total_files: 234,
total_time_tracked: 125000, // seconds
recent_activity: [
{ type: 'task', action: 'completed', title: 'Complete project documentation', timestamp: new Date(Date.now() - 3600000).toISOString() },
{ type: 'bookmark', action: 'added', title: 'SolidJS Documentation', timestamp: new Date(Date.now() - 7200000).toISOString() },
{ type: 'note', action: 'created', title: 'Meeting notes - Q1 planning', timestamp: new Date(Date.now() - 10800000).toISOString() },
{ type: 'file', action: 'uploaded', title: 'project-roadmap.pdf', timestamp: new Date(Date.now() - 14400000).toISOString() },
],
totalTimeTracked: 125000,
averageProductivity: 78,
recentProjects: [
{ name: 'Trackeep Frontend', progress: 85, status: 'active' },
{ name: 'API Documentation', progress: 60, status: 'active' },
{ name: 'Mobile App', progress: 30, status: 'planning' }
],
topCategories: [
{ name: 'Development', count: 45, color: '#3b82f6' },
{ name: 'Documentation', count: 32, color: '#10b981' },
{ name: 'Design', count: 28, color: '#f59e0b' },
{ name: 'Research', count: 21, color: '#8b5cf6' },
{ name: 'Testing', count: 18, color: '#ef4444' }
],
weeklyActivity: [12, 8, 15, 6, 9, 14, 3]
});
const generateMockGitHubRepos = () => [
{
id: 1,
name: 'trackeep',
full_name: 'tdvorak/trackeep',
description: 'Your Self-Hosted Productivity Hub',
private: false,
stargazers_count: 245,
forks_count: 65,
watchers_count: 43,
language: 'Go',
updated_at: new Date().toISOString(),
html_url: 'https://github.com/tdvorak/trackeep',
created_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString(),
size: 1250,
open_issues_count: 12,
default_branch: 'main'
},
{
id: 2,
name: 'solidjs-components',
full_name: 'tdvorak/solidjs-components',
description: 'Reusable SolidJS components library',
private: false,
stargazers_count: 89,
forks_count: 12,
watchers_count: 8,
language: 'TypeScript',
updated_at: new Date(Date.now() - 86400000).toISOString(),
html_url: 'https://github.com/tdvorak/solidjs-components',
created_at: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000).toISOString(),
size: 450,
open_issues_count: 3,
default_branch: 'main'
},
{
id: 3,
name: 'docker-homelab',
full_name: 'tdvorak/docker-homelab',
description: 'Complete Docker setup for home lab environment',
private: false,
stargazers_count: 156,
forks_count: 34,
watchers_count: 21,
language: 'Dockerfile',
updated_at: new Date(Date.now() - 2 * 86400000).toISOString(),
html_url: 'https://github.com/tdvorak/docker-homelab',
created_at: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString(),
size: 890,
open_issues_count: 7,
default_branch: 'main'
},
{
id: 4,
name: 'network-tools',
full_name: 'tdvorak/network-tools',
description: 'Collection of networking utilities and scripts',
private: true,
stargazers_count: 45,
forks_count: 8,
watchers_count: 5,
language: 'Python',
updated_at: new Date(Date.now() - 3 * 86400000).toISOString(),
html_url: 'https://github.com/tdvorak/network-tools',
created_at: new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString(),
size: 234,
open_issues_count: 2,
default_branch: 'develop'
}
];
const generateMockTimeEntries = () => [
{
id: 1,
description: 'Working on Trackeep frontend',
start_time: new Date(Date.now() - 7200000).toISOString(),
end_time: new Date(Date.now() - 3600000).toISOString(),
duration: 3600,
billable: true,
hourly_rate: 75,
task_id: 1,
created_at: new Date(Date.now() - 3600000).toISOString()
},
{
id: 2,
description: 'Meeting with team',
start_time: new Date(Date.now() - 14400000).toISOString(),
end_time: new Date(Date.now() - 12600000).toISOString(),
duration: 1800,
billable: false,
hourly_rate: 0,
task_id: null,
created_at: new Date(Date.now() - 12600000).toISOString()
}
];
const generateMockVideoBookmarks = () => [
{
id: 1,
video_id: 'dQw4w9WgXcQ',
title: 'Never Gonna Give You Up',
description: 'Classic music video from Rick Astley',
channel: 'Rick Astley',
thumbnail: 'https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg',
duration: '3:33',
publishedAt: '2009-10-25',
url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
tags: [
{ id: 1, name: 'music', color: '#ff6b6b' },
{ id: 2, name: 'classic', color: '#4ecdc4' },
{ id: 3, name: '80s', color: '#45b7d1' }
],
created_at: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 2,
video_id: 'jNQXAC9IVRw',
title: 'How to Build a REST API with Go',
description: 'Complete tutorial on building RESTful APIs using Go and Gin framework',
channel: 'Tech with Tim',
thumbnail: 'https://img.youtube.com/vi/jNQXAC9IVRw/maxresdefault.jpg',
duration: '45:20',
publishedAt: '2024-01-15',
url: 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
tags: [
{ id: 4, name: 'programming', color: '#9b59b6' },
{ id: 5, name: 'golang', color: '#3498db' },
{ id: 6, name: 'tutorial', color: '#2ecc71' }
],
created_at: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 3,
video_id: '8S2UDfJ9R8A',
title: 'SolidJS vs React: A Comprehensive Comparison',
description: 'Deep dive into the differences between SolidJS and React frameworks',
channel: 'Frontend Masters',
thumbnail: 'https://img.youtube.com/vi/8S2UDfJ9R8A/maxresdefault.jpg',
duration: '28:15',
publishedAt: '2024-02-01',
url: 'https://www.youtube.com/watch?v=8S2UDfJ9R8A',
tags: [
{ id: 7, name: 'javascript', color: '#f39c12' },
{ id: 8, name: 'frontend', color: '#e74c3c' },
{ id: 9, name: 'framework', color: '#27ae60' }
],
created_at: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 4,
video_id: '5qap5aO4i9A',
title: 'Docker Networking Explained',
description: 'Understanding Docker networking concepts and best practices',
channel: 'DevOps Toolkit',
thumbnail: 'https://img.youtube.com/vi/5qap5aO4i9A/maxresdefault.jpg',
duration: '32:45',
publishedAt: '2024-01-20',
url: 'https://www.youtube.com/watch?v=5qap5aO4i9A',
tags: [
{ id: 10, name: 'docker', color: '#0db7ed' },
{ id: 11, name: 'devops', color: '#ff5722' },
{ id: 12, name: 'networking', color: '#4caf50' }
],
created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString()
},
{
id: 5,
video_id: 'LXb3EKWsInQ',
title: 'Productivity Tips for Developers',
description: 'Essential productivity tips and tools for software developers',
channel: 'Coding Career',
thumbnail: 'https://img.youtube.com/vi/LXb3EKWsInQ/maxresdefault.jpg',
duration: '18:30',
publishedAt: '2024-02-10',
url: 'https://www.youtube.com/watch?v=LXb3EKWsInQ',
tags: [
{ id: 13, name: 'productivity', color: '#ff9800' },
{ id: 14, name: 'development', color: '#607d8b' },
{ id: 15, name: 'tips', color: '#795548' }
],
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
}
];
const generateMockTeams = () => [
{
id: 1,
name: 'Trackeep Workspace',
description: 'Main workspace for Trackeep development',
created_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString()
}
];
const generateMockMembers = () => [
{
user_id: 1,
user: {
id: 1,
username: 'demo',
email: '[email protected]',
full_name: 'Demo User'
},
role: 'owner',
joined_at: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString()
},
{
user_id: 2,
user: {
id: 2,
username: 'alice',
email: '[email protected]',
full_name: 'Alice Johnson'
},
role: 'admin',
joined_at: new Date(Date.now() - 180 * 24 * 60 * 60 * 1000).toISOString()
},
{
user_id: 3,
user: {
id: 3,
username: 'bob',
email: '[email protected]',
full_name: 'Bob Smith'
},
role: 'member',
joined_at: new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString()
},
{
user_id: 4,
user: {
id: 4,
username: 'carol',
email: '[email protected]',
full_name: 'Carol Davis'
},
role: 'member',
joined_at: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
}
];
const generateMockLearningPaths = () => [
{
id: 1,
title: 'SolidJS Mastery',
description: 'Complete guide to SolidJS framework',
category: 'frontend',
difficulty: 'intermediate',
estimated_hours: 20,
progress: 65,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
id: 2,
title: 'Go Backend Development',
description: 'Build scalable backend with Go',
category: 'backend',
difficulty: 'advanced',
estimated_hours: 40,
progress: 30,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
];
const generateMockChatSessions = () => [
{
id: 1,
title: 'Project Planning Discussion',
created_at: new Date(Date.now() - 86400000).toISOString(),
updated_at: new Date(Date.now() - 3600000).toISOString(),
message_count: 15
},
{
id: 2,
title: 'Code Review Help',
created_at: new Date(Date.now() - 172800000).toISOString(),
updated_at: new Date(Date.now() - 7200000).toISOString(),
message_count: 8
}
];
const generateMockRemovedItems = () => [
{
id: '1',
name: 'Old Project Documentation.pdf',
type: 'pdf',
removedAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toLocaleDateString(),
removedBy: 'Demo User',
size: '2.4 MB',
path: '/documents/projects/',
daysInTrash: 5
},
{
id: '2',
name: 'Backup Database.sql',
type: 'sql',
removedAt: new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toLocaleDateString(),
removedBy: 'Demo User',
size: '15.7 MB',
path: '/backups/',
daysInTrash: 12
},
{
id: '3',
name: 'Draft Presentation.pptx',
type: 'pptx',
removedAt: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toLocaleDateString(),
removedBy: 'Demo User',
size: '8.2 MB',
path: '/presentations/',
daysInTrash: 3
},
{
id: '4',
name: 'Old Archive.zip',
type: 'zip',
removedAt: new Date(Date.now() - 25 * 24 * 60 * 60 * 1000).toLocaleDateString(),
removedBy: 'Demo User',
size: '124.5 MB',
path: '/archives/',
daysInTrash: 25
},
{
id: '5',
name: 'Temp Files',
type: 'folder',
removedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toLocaleDateString(),
removedBy: 'Demo User',
size: '456 KB',
path: '/temp/',
daysInTrash: 1
}
];
const generateMockAIProviders = () => [
{
id: 'longcat',
name: 'LongCat AI',
description: 'Fast and efficient AI models',
models: ['LongCat-Flash-Chat', 'LongCat-Flash-Thinking'],
enabled: true,
api_key_configured: true
},
{
id: 'mistral',
name: 'Mistral AI',
description: 'European AI models',
models: ['mistral-small-latest', 'mistral-large-latest'],
enabled: false,
api_key_configured: false
}
];
const generateMockFiles = () => [
{
id: 1,
original_name: 'product-roadmap-q2.pdf',
mime_type: 'application/pdf',
file_size: 2_517_432,
description: 'Q2 roadmap and milestones',
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
},
{
id: 2,
original_name: 'meeting-notes.md',
mime_type: 'text/markdown',
file_size: 15_384,
description: 'Weekly sync notes',
created_at: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString(),
},
{
id: 3,
original_name: 'architecture-diagram.png',
mime_type: 'image/png',
file_size: 842_112,
description: 'System architecture overview',
created_at: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString(),
},
];
const generateMockAISettings = () => ({
mistral: {
enabled: false,
api_key: '',
model: 'mistral-small-latest',
model_thinking: 'mistral-large-latest',
},
grok: {
enabled: false,
api_key: '',
base_url: 'https://api.x.ai/v1',
model: 'grok-4-1-fast-non-reasoning-latest',
model_thinking: 'grok-4-1-fast-reasoning-latest',
},
deepseek: {
enabled: false,
api_key: '',
base_url: 'https://api.deepseek.com',
model: 'deepseek-chat',
model_thinking: 'deepseek-reasoner',
},
ollama: {
enabled: false,
base_url: 'http://localhost:11434',
model: 'llama3.1',
model_thinking: 'llama3.1',
},
longcat: {
enabled: true,
api_key: '********',
base_url: 'https://api.longcat.chat',
openai_endpoint: 'https://api.longcat.chat/openai',
anthropic_endpoint: 'https://api.longcat.chat/anthropic',
model: 'LongCat-Flash-Chat',
model_thinking: 'LongCat-Flash-Thinking',
model_thinking_upgraded: 'LongCat-Flash-Thinking-2601',
format: 'openai',
},
openrouter: {
enabled: false,
api_key: '',
base_url: 'https://openrouter.ai/api',
model: 'openrouter/auto',
model_thinking: 'openrouter/auto',
},
});
const generateMockSearchSettings = () => ({
brave_api_key: '',
brave_search_base_url: 'https://api.search.brave.com/res/v1/web/search',
serper_api_key: '',
serper_base_url: 'https://google.serper.dev/search',
search_api_provider: 'brave',
search_results_limit: 10,
search_cache_ttl: 300,
search_rate_limit: 100,
});
const generateMockEmailSettings = () => ({
smtp_enabled: false,
smtp_host: '',
smtp_port: 587,
smtp_username: '',
smtp_password: '',
smtp_from_email: '',
smtp_from_name: 'Trackeep',
smtp_encryption: 'tls',
oauth_enabled: false,
oauth_provider: 'google',
oauth_client_id: '',
oauth_client_secret: '',
oauth_redirect_uri: '',
});
const generateMockMembersDirectory = () =>
generateMockMembers().map((member) => ({
id: Number(member.user?.id || member.user_id || 0),
username: member.user?.username || `user-${member.user_id}`,
full_name: member.user?.full_name || member.user?.username || 'Member',
name: member.user?.full_name || member.user?.username || 'Member',
email: member.user?.email || '',
role: member.role,
}));
const generateMockChatSessionsExtended = () =>
generateMockChatSessions().map((session) => ({
...session,
include_bookmarks: true,
include_tasks: true,
include_files: true,
include_notes: true,
last_message_at: session.updated_at,
}));
const generateMockChatMessages = (sessionId: number) => [
{
id: sessionId * 100 + 1,
content: 'Can you summarize my pending tasks for this week?',
role: 'user',
created_at: new Date(Date.now() - 50 * 60 * 1000).toISOString(),
token_count: 22,
context_items: ['tasks'],
},
{
id: sessionId * 100 + 2,
content: 'You have 6 open tasks. Two are high priority and due in the next 48 hours.',
role: 'assistant',
created_at: new Date(Date.now() - 49 * 60 * 1000).toISOString(),
token_count: 31,
context_items: ['tasks', 'notes'],
},
];
const generateMockAnalyticsDashboard = (days: number) => {
const safeDays = Number.isFinite(days) && days > 0 ? Math.floor(days) : 30;
const endDate = new Date();
const startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - safeDays + 1);
return {
period: {
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
days: safeDays,
},
summary: {
hours_tracked: 62.5,
tasks_completed: 28,
bookmarks_added: 19,
notes_created: 11,
courses_completed: 2,
github_commits: 34,
},
analytics: [
{
date: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
hours_tracked: 5.5,
tasks_completed: 3,
bookmarks_added: 2,
notes_created: 1,
courses_completed: 0,
github_commits: 4,
study_streak: 5,
productivity_score: 78,
},
{
date: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
hours_tracked: 7.25,
tasks_completed: 4,
bookmarks_added: 3,
notes_created: 2,
courses_completed: 1,
github_commits: 6,
study_streak: 6,
productivity_score: 84,
},
{
date: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(),
hours_tracked: 6.75,
tasks_completed: 5,
bookmarks_added: 1,
notes_created: 2,
courses_completed: 0,
github_commits: 5,
study_streak: 7,
productivity_score: 88,
},
],
productivity_metrics: [
{
period: 'current',
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
total_hours: 62.5,
billable_hours: 48,
non_billable_hours: 14.5,
tasks_completed: 28,
average_task_time: 1.7,
peak_productivity_hour: 10,
focus_score: 81,
efficiency_score: 86,
},
],
learning_analytics: [
{
id: 1,
course: {
title: 'SolidJS Advanced Patterns',
description: 'Improve component architecture and performance',
},
start_date: new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString(),
last_accessed: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
time_spent: 14.25,
progress: 72,
modules_completed: 9,
total_modules: 12,
average_score: 91,
streak_days: 6,
skills_acquired: ['SolidJS', 'State Management', 'Performance Tuning'],
},
],
github_analytics: [
{
date: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
commits: 5,
pull_requests: 1,
issues_opened: 1,
issues_closed: 2,
reviews: 2,
contributions: 11,
languages: {
TypeScript: 65,
Go: 30,
CSS: 5,
},
repositories: ['trackeep', 'solidjs-components'],
},
],
goals: [
{
id: 1,
title: 'Ship messaging improvements',
description: 'Complete remaining demo-mode wiring and QA',
category: 'development',
target_value: 10,
current_value: 7,
unit: 'tasks',
deadline: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
status: 'in_progress',
priority: 'high',
progress: 70,
is_completed: false,
milestones: [
{
id: 1,
title: 'Implement API handlers',
target_value: 1,
current_value: 1,
deadline: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
status: 'completed',
is_completed: true,
},
{
id: 2,
title: 'Regression QA pass',
target_value: 1,
current_value: 0,
deadline: new Date(Date.now() + 3 * 24 * 60 * 60 * 1000).toISOString(),
status: 'pending',
is_completed: false,
},
],
},
],
habit_analytics: [
{
habit_name: 'Daily planning',
start_date: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(),
last_completed: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
streak: 7,
best_streak: 11,
total_days: 30,
completion_rate: 83,
frequency: 'daily',
category: 'productivity',
goal_target: 30,
goal_achieved: false,
},
],
};
};
const generateMockSavedSearches = () => [
{
id: 1,
name: 'Urgent tasks',
query: 'priority:high status:open',
filters: { content_type: 'tasks', priority: ['high', 'urgent'] },
alert: true,
last_run: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString(),
run_count: 12,
is_public: false,
description: 'Track high-priority open tasks',
tags: [
{ id: 1, name: 'tasks', color: '#f59e0b' },
{ id: 2, name: 'priority', color: '#ef4444' },
],
created_at: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
},
{
id: 2,
name: 'API docs notes',
query: 'api documentation',
filters: { content_type: 'notes', tags: ['docs'] },
alert: false,
last_run: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
run_count: 6,
is_public: true,
description: 'Notes related to API and docs',
tags: [{ id: 3, name: 'docs', color: '#3b82f6' }],
created_at: new Date(Date.now() - 45 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
},
];
const generateMockMessagesConversations = () => {
const now = Date.now();
const mkConversation = (id: number, type: 'global' | 'team' | 'group' | 'dm', name: string, topic: string, teamId?: number) => ({
id,
type,
name,
topic,
team_id: teamId ?? null,
created_by: 1,
is_default: id === 1,
is_archived: false,
last_message_at: new Date(now - id * 12 * 60 * 1000).toISOString(),
created_at: new Date(now - id * 30 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date(now - id * 10 * 60 * 1000).toISOString(),
});
const mkMessage = (id: number, conversationId: number, senderId: number, body: string) => ({
id,
conversation_id: conversationId,
sender_id: senderId,
sender: {
id: senderId,
username: senderId === 1 ? 'demo' : senderId === 2 ? 'alice' : 'bob',
full_name: senderId === 1 ? 'Demo User' : senderId === 2 ? 'Alice Johnson' : 'Bob Smith',
},
body,
is_sensitive: false,
edited_at: null,
deleted_at: null,
metadata_json: '{}',
created_at: new Date(now - id * 5 * 60 * 1000).toISOString(),
updated_at: new Date(now - id * 5 * 60 * 1000).toISOString(),
attachments: [],
references: [],
suggestions: [],
reactions: [],
});
const general = mkConversation(1, 'global', 'General', 'Team-wide updates');
const team = mkConversation(2, 'team', 'Engineering', 'Sprint and release discussion', 1);
const dm = mkConversation(3, 'dm', 'Direct: Alice', 'One-on-one updates');
return [
{
conversation: general,
role: 'owner',
unread_count: 0,
last_message: mkMessage(101, general.id, 2, 'Deploy finished successfully.'),
},
{
conversation: team,
role: 'admin',
unread_count: 2,
last_message: mkMessage(102, team.id, 3, 'I pushed a fix for message search.'),
},
{
conversation: dm,
role: 'member',
unread_count: 1,
last_message: mkMessage(103, dm.id, 2, 'Can you review the latest PR?'),
},
];
};
const generateMockMessagesForConversation = (conversationId: number) => {
const now = Date.now();
const mk = (id: number, senderId: number, minutesAgo: number, body: string) => ({
id,
conversation_id: conversationId,
sender_id: senderId,
sender: {
id: senderId,
username: senderId === 1 ? 'demo' : senderId === 2 ? 'alice' : 'bob',
full_name: senderId === 1 ? 'Demo User' : senderId === 2 ? 'Alice Johnson' : 'Bob Smith',
},
body,
is_sensitive: false,
edited_at: null,
deleted_at: null,
metadata_json: '{}',
created_at: new Date(now - minutesAgo * 60 * 1000).toISOString(),
updated_at: new Date(now - minutesAgo * 60 * 1000).toISOString(),
attachments: [],
references: [],
suggestions: [],
reactions: [],
});
return [
mk(conversationId * 1000 + 1, 2, 45, 'Morning standup complete.'),
mk(conversationId * 1000 + 2, 1, 42, 'Great, I will update the board.'),
mk(conversationId * 1000 + 3, 3, 20, 'Search indexing patch is merged.'),
];
};
const generateMockBrowserExtensionApiKeys = () => [
{
id: 1,
name: 'Chrome Extension - Main',
permissions: ['bookmarks:read', 'bookmarks:write', 'files:read'],
is_active: true,
last_used: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
expires_at: new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toISOString(),
created_at: new Date(Date.now() - 12 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
},
];
const generateMockBrowserExtensions = () => [
{
id: 1,
extension_id: 'trackeep-chrome-prod',
name: 'Trackeep Chrome Extension',
is_active: true,
last_seen: new Date(Date.now() - 35 * 60 * 1000).toISOString(),
created_at: new Date(Date.now() - 20 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date(Date.now() - 35 * 60 * 1000).toISOString(),
},
];
// Store original fetch at module level
let originalFetch: typeof fetch | null = null;
// Request cache to prevent duplicate API calls
const requestCache = new Map<string, Promise<Response>>();
const CACHE_TTL = 2000; // 2 seconds
// Generate cache key for requests
const getCacheKey = (url: string, options?: RequestInit): string => {
const method = options?.method || 'GET';
const body = options?.body || '';
return `${method}:${url}:${body}`;
};
const jsonResponse = (payload: unknown, status: number = 200): Response =>
new Response(JSON.stringify(payload), {
status,
headers: { 'Content-Type': 'application/json' },
});
const parseBody = (options?: RequestInit): Record<string, any> => {
if (!options?.body || typeof options.body !== 'string') {
return {};
}
try {
return JSON.parse(options.body);
} catch {
return {};
}
};
// Demo mode fetch interceptor
export const demoFetch = async (url: string, options?: RequestInit): Promise<Response> => {
const shouldUseReal = shouldUseRealAPIs();
if (shouldUseReal) {
if (url.includes('youtube') && Math.random() < 0.02) {
}
if (url.includes('youtube')) {
const cacheKey = getCacheKey(url, options);
const cachedRequest = requestCache.get(cacheKey);
if (cachedRequest) {
return cachedRequest;
}
const requestPromise = (originalFetch || window.fetch)(url, options);
requestCache.set(cacheKey, requestPromise);
setTimeout(() => {
requestCache.delete(cacheKey);
}, CACHE_TTL);
return requestPromise;
}
return (originalFetch || window.fetch)(url, options);
}
if (!isDemoMode()) {
return (originalFetch || window.fetch)(url, options);
}
let path = url;
let searchParams = new URLSearchParams();
try {
const parsed = new URL(url, window.location.origin);
path = parsed.pathname;
searchParams = parsed.searchParams;
} catch {
// keep defaults
}
const method = (options?.method || 'GET').toUpperCase();
const body = parseBody(options);
await new Promise((resolve) => setTimeout(resolve, 180));
// Auth endpoints
if (path.includes('/api/v1/auth/login') || path.includes('/api/v1/auth/login-totp') || path.includes('/api/v1/auth/register')) {
const user = {
id: 1,
email: '[email protected]',
username: 'demo',
full_name: 'Demo User',
theme: 'dark',
github_id: 123456,
created_at: new Date(Date.now() - 120 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString(),
};
return jsonResponse({
token: `demo-token-${Date.now()}`,
user,
});
}
if (path.includes('/api/v1/auth/check-users')) {
return jsonResponse({ hasUsers: true });
}
if (path.includes('/api/v1/auth/logout')) {
return jsonResponse({ message: 'Logged out (demo mode)' });
}
if (path.includes('/api/v1/auth/me')) {
return jsonResponse({
user: {
id: 1,
email: '[email protected]',
username: 'demo',
full_name: 'Demo User',
theme: 'dark',
github_id: 123456,
},
});
}
if (path.includes('/api/v1/auth/profile') && method === 'PUT') {
return jsonResponse({
user: {
id: 1,
email: '[email protected]',
username: 'demo',
full_name: typeof body.fullName === 'string' && body.fullName.trim() ? body.fullName.trim() : 'Demo User',
theme: body.theme === 'light' ? 'light' : 'dark',
updated_at: new Date().toISOString(),
},
});
}
if (path.includes('/api/v1/auth/password') && method === 'PUT') {
return jsonResponse({ message: 'Password changed successfully (demo mode)' });
}
if (path.includes('/api/v1/auth/password-reset/confirm') && method === 'POST') {
return jsonResponse({ message: 'Password reset successful (demo mode)' });
}
if (path.includes('/api/v1/auth/password-reset') && method === 'POST') {
return jsonResponse({ message: 'Password reset code sent (demo mode)' });
}
if (path.includes('/api/v1/auth/ai/settings')) {
if (method === 'GET') {
return jsonResponse(generateMockAISettings());
}
if (method === 'PUT') {
return jsonResponse(body);
}
}
if (path.includes('/api/v1/auth/search/settings')) {
if (method === 'GET') {
return jsonResponse(generateMockSearchSettings());
}
if (method === 'PUT') {
return jsonResponse(body);
}
}
if (path.includes('/api/v1/auth/email/settings')) {
if (method === 'GET') {
return jsonResponse(generateMockEmailSettings());
}
if (method === 'PUT') {
return jsonResponse({ ...generateMockEmailSettings(), ...body });
}
}
if (path.includes('/api/v1/auth/email/test') && method === 'POST') {
return jsonResponse({ message: 'Test email sent successfully (demo mode)' });
}
// Dashboard and analytics
if (path.includes('/api/v1/dashboard/stats')) {
return jsonResponse(generateMockStats());
}
if (path.includes('/api/v1/analytics/dashboard')) {
const days = Number(searchParams.get('days') || '30');
return jsonResponse(generateMockAnalyticsDashboard(days));
}
// AI endpoints
if (path.includes('/api/v1/ai/providers') || path.includes('/api/ai/providers')) {
return jsonResponse({ providers: generateMockAIProviders() });
}
if (path.includes('/api/v1/ai/chat') && method === 'POST') {
const prompt = typeof body.message === 'string' ? body.message : '';
return jsonResponse({
response: `Demo AI response for: "${prompt || 'your message'}"`,
content: `Demo AI response for: "${prompt || 'your message'}"`,
provider: body.provider || 'longcat',
model: body.model || 'LongCat-Flash-Chat',
});
}
// Files endpoints
if (path.includes('/api/v1/files/upload') && method === 'POST') {
return jsonResponse({
id: Date.now(),
original_name: 'uploaded-file.txt',
mime_type: 'text/plain',
file_size: 2048,
created_at: new Date().toISOString(),
description: 'Uploaded from demo mode',
}, 201);
}
if (/^\/api\/v1\/files\/\d+\/download$/.test(path)) {
return jsonResponse({ url: '#', message: 'Demo download link generated' });
}
if (path.includes('/api/v1/files') && method === 'GET') {
return jsonResponse(generateMockFiles());
}
// Team/member endpoints (specific before generic /teams)
if (/^\/api\/v1\/teams\/\d+\/members\/\d+$/.test(path) && method === 'DELETE') {
return jsonResponse({ message: 'Member removed successfully (demo mode)' });
}
if (/^\/api\/v1\/teams\/\d+\/invite$/.test(path) && method === 'POST') {
return jsonResponse({ message: 'Invitation sent successfully (demo mode)' });
}
if (/^\/api\/v1\/teams\/\d+\/members$/.test(path) && method === 'GET') {
return jsonResponse({ members: generateMockMembers() });
}
if (path.includes('/api/v1/members') && method === 'GET') {
return jsonResponse({ members: generateMockMembersDirectory() });
}
if (path.includes('/api/v1/teams') && method === 'GET') {
return jsonResponse({ teams: generateMockTeams() });
}
// GitHub endpoints
if (path.includes('/api/v1/github/repos') && method === 'GET') {
return jsonResponse({ repos: generateMockGitHubRepos() });
}
// Time entries
if (path.includes('/api/v1/time-entries') && method === 'GET') {
return jsonResponse({ time_entries: generateMockTimeEntries() });
}
if (path.includes('/api/v1/time-entries') && method === 'POST') {
return jsonResponse({ ...body, id: Date.now() }, 201);
}
// Bookmarks/tasks/notes GET
if (path.includes('/api/v1/bookmarks') && method === 'GET') {
const { getMockBookmarks } = await import('./mockData');
const mockBookmarks = getMockBookmarks().map((bookmark, index) => ({
id: index + 1,
title: bookmark.title,
url: bookmark.url,
description: bookmark.description,
tags: bookmark.tags,
created_at: bookmark.createdAt,
is_favorite: bookmark.tags.some((tag) => tag.name === 'important' || tag.name === 'favorite'),
favicon: bookmark.favicon,
screenshot: bookmark.screenshot,
screenshot_medium: bookmark.screenshot,
}));
return jsonResponse(mockBookmarks);
}
if (path.includes('/api/v1/tasks') && method === 'GET') {
const { getMockTasks } = await import('./mockData');
const mockTasks = getMockTasks().map((task, index) => {
const status = task.status === 'completed' ? 'completed' : 'pending';
return {
id: index + 1,
title: task.title,
description: task.description,
completed: status === 'completed',
status,
priority: task.priority,
createdAt: task.createdAt,
created_at: task.createdAt,
dueDate: task.dueDate,
due_date: task.dueDate,
};
});
return jsonResponse(mockTasks);
}
if (path.includes('/api/v1/notes') && method === 'GET') {
const { getMockNotes } = await import('./mockData');
const mockNotes = getMockNotes().map((note, index) => ({
id: index + 1,
title: note.title,
content: note.content,
tags: note.tags,
created_at: note.createdAt,
updated_at: note.updatedAt,
pinned: note.tags.some((tag) => tag.name === 'pinned'),
is_pinned: note.tags.some((tag) => tag.name === 'pinned'),
attachments: note.attachments || [],
}));
return jsonResponse(mockNotes);
}
// Bookmark/task/note mutations
if (path.includes('/api/v1/bookmarks') && method === 'POST') {
return jsonResponse({
id: Date.now(),
title: body.title || 'Untitled bookmark',
url: body.url || '',
description: body.description || '',
tags: body.tags || [],
created_at: new Date().toISOString(),
is_favorite: Boolean(body.is_favorite),
favicon: body.favicon || '',
screenshot: body.screenshot || '',
screenshot_medium: body.screenshot_medium || '',
}, 201);
}
if (path.includes('/api/v1/tasks') && method === 'POST') {
const completed = Boolean(body.completed);
return jsonResponse({
id: Date.now(),
title: body.title || 'Untitled task',
description: body.description || '',
completed,
status: completed ? 'completed' : 'pending',
priority: body.priority || 'medium',
createdAt: new Date().toISOString(),
created_at: new Date().toISOString(),
dueDate: body.dueDate || '',
due_date: body.dueDate || '',
}, 201);
}
if (path.includes('/api/v1/notes') && method === 'POST') {
return jsonResponse({
id: Date.now(),
title: body.title || 'Untitled note',
content: body.content || '',
tags: body.tags || [],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
pinned: Boolean(body.pinned),
is_pinned: Boolean(body.pinned),
attachments: body.attachments || [],
}, 201);
}
if (path.includes('/api/v1/bookmarks') && method === 'PUT') {
const pathParts = path.split('/');
const idFromPath = Number(pathParts[pathParts.length - 1] || 0);
return jsonResponse({
id: idFromPath || body.id || Date.now(),
title: body.title || 'Untitled bookmark',
url: body.url || '',
description: body.description || '',
tags: body.tags || [],
created_at: body.created_at || new Date().toISOString(),
is_favorite: Boolean(body.is_favorite),
favicon: body.favicon || '',
screenshot: body.screenshot || '',
screenshot_medium: body.screenshot_medium || '',
});
}
if (path.includes('/api/v1/tasks') && method === 'PUT') {
const pathParts = path.split('/');
const idFromPath = Number(pathParts[pathParts.length - 1] || 0);
const completed = Boolean(body.completed);
return jsonResponse({
id: idFromPath || body.id || Date.now(),
title: body.title || 'Untitled task',
description: body.description || '',
completed,
status: completed ? 'completed' : 'pending',
priority: body.priority || 'medium',
createdAt: body.createdAt || new Date().toISOString(),
dueDate: body.dueDate || '',
due_date: body.dueDate || '',
});
}
if (path.includes('/api/v1/notes/') && path.endsWith('/pin') && method === 'PUT') {
const parts = path.split('/');
const noteId = Number(parts[parts.length - 2] || 0);
return jsonResponse({
id: noteId,
pinned: Boolean(body.pinned),
is_pinned: Boolean(body.pinned),
updated_at: new Date().toISOString(),
});
}
if (path.includes('/api/v1/notes') && method === 'PUT') {
const pathParts = path.split('/');
const idFromPath = Number(pathParts[pathParts.length - 1] || 0);
return jsonResponse({
id: idFromPath || body.id || Date.now(),
title: body.title || 'Untitled note',
content: body.content || '',
tags: body.tags || [],
created_at: body.created_at || new Date().toISOString(),
updated_at: new Date().toISOString(),
pinned: Boolean(body.pinned),
is_pinned: Boolean(body.pinned),
attachments: body.attachments || [],
});
}
if (path.includes('/api/v1/bookmarks') && method === 'DELETE') {
return jsonResponse({ message: 'Bookmark deleted (demo mode)' });
}
if (path.includes('/api/v1/tasks') && method === 'DELETE') {
return jsonResponse({ message: 'Task deleted (demo mode)' });
}
if (path.includes('/api/v1/notes') && method === 'DELETE') {
return jsonResponse({ message: 'Note deleted (demo mode)' });
}
// YouTube/video endpoints
if (path.includes('/api/v1/video-bookmarks')) {
if (method === 'GET') {
return jsonResponse({ bookmarks: generateMockVideoBookmarks() });
}
if (method === 'POST') {
return jsonResponse({ id: Date.now(), message: 'Bookmark created successfully (demo mode)' }, 201);
}
}
if (path.includes('/api/v1/youtube/videos') && method === 'GET') {
return jsonResponse(generateMockVideoBookmarks());
}
if (path.includes('/api/v1/youtube/search')) {
const { getMockVideos } = await import('./mockData');
const query = String(body.query || '').toLowerCase();
const mockVideos = getMockVideos()
.filter((video) =>
video.title.toLowerCase().includes(query) ||
video.description.toLowerCase().includes(query) ||
video.channel.toLowerCase().includes(query)
)
.slice(0, 10)
.map((video) => ({
id: video.id,
title: video.title,
channel_title: video.channel,
duration: video.duration,
published_at: video.publishedAt,
view_count: Math.floor(Math.random() * 100000) + 1000,
}));
return jsonResponse({ videos: mockVideos });
}
if (path.includes('/api/v1/youtube/channel-videos')) {
const { getMockVideos } = await import('./mockData');
const mockVideos = getMockVideos().slice(0, 5).map((video) => ({
video_id: video.id,
title: video.title,
channel: video.channel,
length: video.duration,
views: Math.floor(Math.random() * 100000) + 1000,
published_date: video.publishedAt,
published_text: new Date(video.publishedAt).toLocaleDateString(),
}));
return jsonResponse({ videos: mockVideos });
}
if (path.includes('/api/v1/youtube/video-details')) {
return jsonResponse(generateMockVideoBookmarks()[0]);
}
if (path.includes('/api/v1/youtube/predefined-channels')) {
return jsonResponse(generateMockVideoBookmarks());
}
// Learning paths
if (path.includes('/api/v1/learning-paths/categories')) {
return jsonResponse(['frontend', 'backend', 'fullstack', 'devops', 'mobile', 'design']);
}
if (path.includes('/api/v1/learning-paths')) {
return jsonResponse(generateMockLearningPaths());
}
// Chat endpoints
const chatMessagesMatch = path.match(/^\/api\/v1\/chat\/sessions\/(\d+)\/messages$/);
if (chatMessagesMatch && method === 'GET') {
const sessionId = Number(chatMessagesMatch[1]);
return jsonResponse(generateMockChatMessages(sessionId));
}
const chatSessionDeleteMatch = path.match(/^\/api\/v1\/chat\/sessions\/(\d+)$/);
if (chatSessionDeleteMatch && method === 'DELETE') {
return jsonResponse({ message: 'Session deleted (demo mode)' });
}
if (path === '/api/v1/chat/sessions' && method === 'GET') {
return jsonResponse(generateMockChatSessionsExtended());
}
if (path === '/api/v1/chat/send' && method === 'POST') {
const sessionId = Number(body.session_id || generateMockChatSessionsExtended()[0].id);
return jsonResponse({
session_id: sessionId,
message: `Demo assistant reply to: ${body.message || 'your prompt'}`,
timestamp: new Date().toISOString(),
});
}
// Messages module endpoints
if (path.startsWith('/api/v1/messages')) {
const conversations = generateMockMessagesConversations();
const defaultConversation = conversations[0];
if (path === '/api/v1/messages/conversations' && method === 'GET') {
return jsonResponse({ conversations });
}
if (path === '/api/v1/messages/conversations' && method === 'POST') {
const type = typeof body.type === 'string' ? body.type : 'group';
const conversation = {
id: Date.now(),
type,
name: typeof body.name === 'string' && body.name.trim() ? body.name.trim() : 'New conversation',
topic: typeof body.topic === 'string' ? body.topic : '',
team_id: Number(body.team_id || 0) || null,
created_by: 1,
is_default: false,
is_archived: false,
last_message_at: new Date().toISOString(),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
return jsonResponse({ conversation }, 201);
}
const conversationDetailMatch = path.match(/^\/api\/v1\/messages\/conversations\/(\d+)$/);
if (conversationDetailMatch && method === 'GET') {
const conversationId = Number(conversationDetailMatch[1]);
const found = conversations.find((entry) => entry.conversation.id === conversationId)?.conversation || defaultConversation.conversation;
const members = generateMockMembers().map((member, index) => ({
id: index + 1,
conversation_id: conversationId,
user_id: Number(member.user?.id || member.user_id || index + 1),
role: member.role,
joined_at: member.joined_at,
last_read_message_id: null,
last_read_at: null,
muted_until: null,
is_hidden: false,
user: {
id: Number(member.user?.id || member.user_id || index + 1),
username: member.user?.username || `member-${index + 1}`,
full_name: member.user?.full_name || member.user?.username || 'Member',
avatar_url: '',
},
}));
return jsonResponse({
conversation: found,
membership: members.find((member) => member.user_id === 1) || members[0],
members,
});
}
const conversationMessagesMatch = path.match(/^\/api\/v1\/messages\/conversations\/(\d+)\/messages$/);
if (conversationMessagesMatch && method === 'GET') {
const conversationId = Number(conversationMessagesMatch[1]);
return jsonResponse({
messages: generateMockMessagesForConversation(conversationId),
next_cursor: null,
});
}
if (conversationMessagesMatch && method === 'POST') {
const conversationId = Number(conversationMessagesMatch[1]);
const newMessage = {
id: Date.now(),
conversation_id: conversationId,
sender_id: 1,
sender: {
id: 1,
username: 'demo',
full_name: 'Demo User',
avatar_url: '',
},
body: typeof body.body === 'string' ? body.body : '',
is_sensitive: false,
edited_at: null,
deleted_at: null,
metadata_json: JSON.stringify(body.metadata || {}),
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
attachments: Array.isArray(body.attachments) ? body.attachments : [],
references: Array.isArray(body.references) ? body.references : [],
suggestions: [],
reactions: [],
};
return jsonResponse({ message: newMessage }, 201);
}
const messageMutationMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)$/);
if (messageMutationMatch && method === 'PATCH') {
const messageId = Number(messageMutationMatch[1]);
return jsonResponse({
message: {
id: messageId,
conversation_id: 1,
sender_id: 1,
body: typeof body.body === 'string' ? body.body : '',
is_sensitive: false,
edited_at: new Date().toISOString(),
deleted_at: null,
created_at: new Date(Date.now() - 10 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString(),
},
});
}
if (messageMutationMatch && method === 'DELETE') {
return jsonResponse({ message: 'Message deleted (demo mode)' });
}
const reactionAddMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/reactions$/);
if (reactionAddMatch && method === 'POST') {
return jsonResponse({
reaction: {
id: Date.now(),
message_id: Number(reactionAddMatch[1]),
user_id: 1,
emoji: body.emoji || ':+1:',
},
}, 201);
}
const reactionDeleteMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/reactions\/(.+)$/);
if (reactionDeleteMatch && method === 'DELETE') {
return jsonResponse({ message: 'Reaction removed (demo mode)' });
}
if (path === '/api/v1/messages/messages/search' && method === 'POST') {
const query = typeof body.query === 'string' ? body.query : '';
const results = generateMockMessagesForConversation(1).filter((message) =>
query ? message.body.toLowerCase().includes(query.toLowerCase()) : true
);
return jsonResponse({
results,
total: results.length,
limit: Number(body.limit || 50),
offset: Number(body.offset || 0),
});
}
const suggestionsMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/suggestions$/);
if (suggestionsMatch && method === 'GET') {
const messageId = Number(suggestionsMatch[1]);
return jsonResponse({
suggestions: [
{
id: 1,
message_id: messageId,
type: 'summarize',
payload_json: JSON.stringify({ summary: 'Condense this message to key bullets.' }),
status: 'pending',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
],
});
}
const suggestionActionMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/suggestions\/(\d+)\/(accept|dismiss)$/);
if (suggestionActionMatch && method === 'POST') {
return jsonResponse({ message: 'Suggestion updated (demo mode)' });
}
const revealSensitiveMatch = path.match(/^\/api\/v1\/messages\/messages\/(\d+)\/reveal-sensitive$/);
if (revealSensitiveMatch && method === 'POST') {
const messageId = Number(revealSensitiveMatch[1]);
return jsonResponse({
message_id: messageId,
plaintext: 'demo-sensitive-content',
});
}
if (path === '/api/v1/messages/password-vault/items' && method === 'GET') {
return jsonResponse({
items: [
{
id: 1,
label: 'Demo Database Password',
owner_user_id: 1,
source_message_id: 1001,
last_accessed_at: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),
shared: true,
allow_reveal: true,
expires_at: null,
target_conversation_id: 1,
},
],
});
}
if (path === '/api/v1/messages/password-vault/items' && method === 'POST') {
return jsonResponse({
item: {
id: Date.now(),
...body,
owner_user_id: 1,
shared: false,
allow_reveal: true,
},
}, 201);
}
const vaultRevealMatch = path.match(/^\/api\/v1\/messages\/password-vault\/items\/(\d+)\/reveal$/);
if (vaultRevealMatch && method === 'POST') {
return jsonResponse({
id: Number(vaultRevealMatch[1]),
label: 'Demo Secret',
secret: 'demo-secret-value',
notes: 'Demo reveal payload',
});
}
const vaultShareMatch = path.match(/^\/api\/v1\/messages\/password-vault\/items\/(\d+)\/share$/);
if (vaultShareMatch && method === 'POST') {
return jsonResponse({ message: 'Vault item shared (demo mode)' });
}
const vaultUnshareMatch = path.match(/^\/api\/v1\/messages\/password-vault\/items\/(\d+)\/unshare$/);
if (vaultUnshareMatch && method === 'POST') {
return jsonResponse({ message: 'Vault item unshared (demo mode)' });
}
}
// Browser extension endpoints
if (path === '/api/v1/browser-extension/validate' && method === 'GET') {
return jsonResponse({
valid: true,
permissions: ['bookmarks:read', 'bookmarks:write', 'files:read'],
user: { id: 1, username: 'demo' },
});
}
if (path === '/api/v1/browser-extension/api-keys' && method === 'GET') {
return jsonResponse(generateMockBrowserExtensionApiKeys());
}
if (path === '/api/v1/browser-extension/extensions' && method === 'GET') {
return jsonResponse(generateMockBrowserExtensions());
}
if (path === '/api/v1/browser-extension/api-keys/generate' && method === 'POST') {
return jsonResponse({
id: Date.now(),
name: body.name || 'New API Key',
permissions: Array.isArray(body.permissions) ? body.permissions : [],
is_active: true,
key: `tk_demo_${Date.now()}`,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}, 201);
}
if (/^\/api\/v1\/browser-extension\/api-keys\/\d+$/.test(path) && method === 'DELETE') {
return jsonResponse({ message: 'API key revoked successfully (demo mode)' });
}
if (/^\/api\/v1\/browser-extension\/extensions\/[^/]+$/.test(path) && method === 'DELETE') {
return jsonResponse({ message: 'Extension revoked successfully (demo mode)' });
}
// Search endpoints
if (path.includes('/api/v1/search/semantic') && method === 'POST') {
const query = typeof body.query === 'string' ? body.query : '';
const results = [
{
id: 1,
type: 'note',
title: `Semantic result for "${query || 'query'}"`,
description: 'Demo semantic search match from notes',
content: 'This is a semantic match generated for demo mode.',
tags: [{ id: 1, name: 'semantic', color: '#3b82f6' }],
created_at: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString(),
score: 0.92,
similarity: 0.92,
},
{
id: 2,
type: 'bookmark',
title: `Related bookmark: ${query || 'topic'}`,
description: 'Demo semantic match from bookmarks',
content: 'Bookmark content excerpt for semantic demo.',
tags: [{ id: 2, name: 'bookmark', color: '#10b981' }],
created_at: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString(),
url: 'https://example.com',
score: 0.84,
similarity: 0.84,
},
];
return jsonResponse({
results,
took: 42,
});
}
if (path.includes('/api/v1/search/enhanced') && method === 'POST') {
const query = typeof body.query === 'string' ? body.query : '';
const filters = typeof body === 'object' && body ? body : {};
const results = [
{
id: 101,
type: 'task',
title: `Task match for "${query || 'query'}"`,
description: 'Demo enhanced search task result',
content: 'Follow up on API integration tests.',
tags: [{ id: 11, name: 'task', color: '#f59e0b' }],
created_at: new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString(),
status: 'pending',
priority: 'high',
score: 0.88,
},
{
id: 102,
type: 'note',
title: `Note match for "${query || 'query'}"`,
description: 'Demo enhanced search note result',
content: 'Implementation notes for demo-mode endpoint wiring.',
tags: [{ id: 12, name: 'notes', color: '#8b5cf6' }],
created_at: new Date(Date.now() - 72 * 60 * 60 * 1000).toISOString(),
updated_at: new Date().toISOString(),
score: 0.81,
},
];
return jsonResponse({
results,
total: results.length,
query,
filters,
took: 37,
suggestions: query ? [`${query} settings`, `${query} notes`] : [],
aggregations: {
tasks: 1,
notes: 1,
},
});
}
if (path === '/api/v1/search/saved/tags' && method === 'GET') {
return jsonResponse({
tags: [
{ id: 1, name: 'tasks', color: '#f59e0b' },
{ id: 2, name: 'docs', color: '#3b82f6' },
],
});
}
if (/^\/api\/v1\/search\/saved\/\d+\/run$/.test(path) && method === 'POST') {
return jsonResponse({
results: [
{
id: 1,
type: 'task',
title: 'Demo saved search result',
description: 'Result returned by demo saved-search run endpoint',
content: 'Saved search execution result',
tags: [{ id: 1, name: 'saved', color: '#10b981' }],
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
score: 0.8,
},
],
total: 1,
took: 28,
});
}
if (path === '/api/v1/search/saved' && method === 'GET') {
return jsonResponse({ saved_searches: generateMockSavedSearches() });
}
if (path === '/api/v1/search/saved' && method === 'POST') {
return jsonResponse({
saved_search: {
id: Date.now(),
...body,
run_count: 0,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
},
}, 201);
}
if (/^\/api\/v1\/search\/saved\/\d+$/.test(path) && method === 'PUT') {
return jsonResponse({
saved_search: {
...body,
updated_at: new Date().toISOString(),
},
});
}
if (/^\/api\/v1\/search\/saved\/\d+$/.test(path) && method === 'DELETE') {
return jsonResponse({ message: 'Saved search deleted (demo mode)' });
}
// Search web/news endpoints
if (path.includes('/api/v1/search/web') || path.includes('/res/v1/web/search')) {
const queryParam = (searchParams.get('q') || body.query || '').trim();
if (!queryParam) {
return jsonResponse({ error: 'Query parameter required' }, 400);
}
const mockSearchResults = [
{
title: `${queryParam} - Demo Search Result 1`,
url: `https://demo.example.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
description: `This is a demo search result for "${queryParam}" showing how the search functionality works in demo mode.`,
published_date: new Date().toISOString().split('T')[0],
language: 'English',
family_friendly: true,
type: 'web',
subtype: 'search',
},
{
title: `${queryParam} - Demo Search Result 2`,
url: `https://demo-search.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
description: `Another demo search result for "${queryParam}" demonstrating the search interface in demo mode.`,
published_date: new Date(Date.now() - 86400000).toISOString().split('T')[0],
language: 'English',
family_friendly: true,
type: 'web',
subtype: 'search',
},
{
title: `Learn more about ${queryParam}`,
url: `https://demo-learning.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
description: `Educational content about ${queryParam} for demo purposes. This shows how search results can include learning resources.`,
published_date: new Date(Date.now() - 172800000).toISOString().split('T')[0],
language: 'English',
family_friendly: true,
type: 'web',
subtype: 'educational',
},
];
return jsonResponse({
results: mockSearchResults,
web: { results: mockSearchResults },
query: { original: queryParam, display: queryParam },
mixed: { results: mockSearchResults },
});
}
if (path.includes('/api/v1/search/news') || path.includes('/res/v1/news/search')) {
const queryParam = (searchParams.get('q') || body.query || '').trim();
if (!queryParam) {
return jsonResponse({ error: 'Query parameter required' }, 400);
}
const mockNewsResults = [
{
title: `Breaking News: ${queryParam} Update`,
url: `https://demo-news.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
description: `Latest news about ${queryParam} - this is a demo news search result showing how news search works in demo mode.`,
published_date: new Date().toISOString().split('T')[0],
language: 'English',
family_friendly: true,
type: 'news',
subtype: 'article',
},
{
title: `${queryParam} - Industry Report`,
url: `https://demo-industry.com/${queryParam.toLowerCase().replace(/\s+/g, '-')}`,
description: `Industry analysis and reports about ${queryParam}. This demo result shows how news search can include industry content.`,
published_date: new Date(Date.now() - 86400000).toISOString().split('T')[0],
language: 'English',
family_friendly: true,
type: 'news',
subtype: 'report',
},
];
return jsonResponse({
results: mockNewsResults,
news: { results: mockNewsResults },
query: { original: queryParam, display: queryParam },
mixed: { results: mockNewsResults },
});
}
// Removed items / trash
if (path.includes('/api/v1/trash') || path.includes('/api/v1/removed-items') || path === '/trash') {
return jsonResponse(generateMockRemovedItems());
}
// Update checking endpoints
if (path.includes('/api/updates/check')) {
return jsonResponse({
updateAvailable: false,
currentVersion: '1.0.0-demo',
latestVersion: '1.0.0-demo',
});
}
if (path.includes('/api/updates/progress')) {
return jsonResponse({
progress: 0,
downloading: false,
installing: false,
completed: false,
error: null,
});
}
return jsonResponse(
{
error: `No demo handler for ${method} ${path}`,
},
404
);
};
// Override global fetch for demo mode
export const initializeDemoMode = () => {
if (isDemoMode()) {
// Store original fetch to use for real API calls and restore later if needed
originalFetch = window.fetch;
window.fetch = demoFetch as typeof fetch;
return originalFetch;
}
return null;
};