ELK Integration Guide
Transform your ELK Mastodon client into an AI-powered social media experience with Corgi's seamless recommendation engine. This guide covers the complete integration that makes AI recommendations appear naturally in timelines while maintaining transparency and user control.
What is "Seamless" Integration?
The ELK integration follows a "seamless" philosophy where Corgi enhances timelines invisibly:
- Zero Configuration: Users connect to standard Mastodon servers normally
- Transparent Operation: AI recommendations blend naturally with regular posts
- Graceful Degradation: ELK works perfectly even if Corgi is offline
- Privacy-First: All processing happens locally with user consent
Architecture Overview
The integration consists of four main layers:
graph TB
subgraph UI [User Interface Layer]
A[TimelineHome.vue]
B[TimelineCorgi.vue]
C[StatusCard.vue]
D[StatusRecommendationTag.vue]
end
subgraph ENH [Timeline Enhancement Layer]
E[reorderAndFilter]
F[enhanceTimelineWithCorgi]
G[handleRemovePost]
end
subgraph COMP [Composable Integration Layer]
H[corgi-seamless.ts]
I[corgi-status-actions.ts]
J[User Detection and Privacy]
K[Caching and Performance]
end
subgraph API [Backend API Layer]
L[seamless endpoint]
M[preferences endpoint]
N[Privacy Layer and Learning System]
end
UI --> ENH
ENH --> COMP
COMP --> API
classDef ui fill:#e3f2fd,stroke:#1976d2,color:#000
classDef enhancement fill:#f3e5f5,stroke:#7b1fa2,color:#000
classDef composable fill:#e8f5e8,stroke:#388e3c,color:#000
classDef backend fill:#fff3e0,stroke:#f57c00,color:#000
class A,B,C,D ui
class E,F,G enhancement
class H,I,J,K composable
class L,M,N backend
Prerequisites
- ELK Instance: Running at
localhost:5314(or your preferred port) - Corgi Backend: Running at
localhost:5002with seamless endpoints - Node.js Environment: For ELK development and customization
- ELK Source Access:
/Users/andrewnordstrom/Elk_Corgi/ELK
Integration Components Deep Dive
1. Core Composable (corgi-seamless.ts)
The heart of the integration is the Vue 3 composable that handles all Corgi functionality:
// Multi-method user detection
function getCurrentUserId(): string {
// Method 1: ELK global state
const elkGlobal = (window as any)?.$elk?.currentUser?.account?.acct
// Method 2: Nuxt app context
const nuxtApp = (window as any)?.$nuxt?.$currentUser?.account?.acct
// Method 3: localStorage user handle
const currentUserHandle = localStorage.getItem('elk-current-user-handle')
// 5 additional fallback methods...
return userId || 'anonymous'
}
// Timeline enhancement with intelligent injection
export function useCorgiSeamless() {
async function enhanceTimeline(
type: 'home' | 'local' | 'public',
statuses: mastodon.v1.Status[],
userId?: string
): Promise<mastodon.v1.Status[]> {
// Fetch contextual recommendations
const recommendations = await fetchRecommendations(20, userId)
// Transform to Mastodon-compatible format
const transformed = recommendations.map(transformToMastodonPost)
// Inject intelligently into timeline
return injectRecommendations(statuses, transformed, 0.3) // 30% ratio
}
}
2. Timeline Components
Standard Timeline Enhancement (TimelineHome.vue)
<script setup lang="ts">
import { useCorgiSeamless } from '../../../composables/corgi-seamless'
const { enhanceTimeline, recordInteraction, isEnabled } = useCorgiSeamless()
const enhancedTimeline = ref<mastodon.v1.Status[]>([])
function reorderAndFilter(items: mastodon.v1.Status[]) {
// Apply standard timeline reordering
const reordered = reorderedTimeline(items, 'home')
// Check for cached enhanced timeline
if (enhancedTimeline.value.length > reordered.length) {
return enhancedTimeline.value // Use cached enhanced version
}
// Start background enhancement
enhanceTimelineWithCorgi(reordered).then(enhanced => {
enhancedTimeline.value = enhanced
})
return reordered // Return current timeline while enhancing
}
async function enhanceTimelineWithCorgi(items: mastodon.v1.Status[]) {
if (!isEnabled()) return items
const enhanced = await enhanceTimeline('home', items, currentUser.value?.account?.id)
return enhanced
}
</script>
<template>
<div>
<TimelinePaginator
:preprocess="reorderAndFilter"
context="home"
@status-interaction="handleStatusInteraction"
/>
</div>
</template>
Dedicated Corgi Timeline (TimelineCorgi.vue)
<script setup lang="ts">
// Custom paginator for Corgi recommendations
const createCorgiPaginator = (): mastodon.Paginator<mastodon.v1.Status[]> => {
return {
async next() {
const posts = await fetchTimeline('home', {
limit: 20,
max_id: currentMaxId.value
})
// Deduplication and pagination logic
const newPosts = posts.filter(post => !allFetchedPostIds.value.has(post.id))
if (newPosts.length === 0) {
hasMorePosts.value = false
return { value: [], done: true }
}
return { value: newPosts, done: false }
}
}
}
function handleRemovePost(postId: string) {
removedPostIds.value.add(postId)
// Force timeline refresh
paginator.value = createCorgiPaginator()
}
</script>
<template>
<div>
<div v-if="!currentUser?.account" class="anonymous-banner">
<h3>📚 Anonymous Corgi Demo</h3>
<p>You're viewing Corgi recommendations without being logged in.</p>
</div>
<TimelinePaginator
:paginator="paginator"
:preprocess="reorderAndFilter"
context="public"
@remove-post="handleRemovePost"
/>
</div>
</template>
3. Enhanced Status Display (StatusCard.vue)
<script setup lang="ts">
const emit = defineEmits<{
removePost: [postId: string]
}>()
const isRemoving = ref(false)
const shouldHide = ref(false)
function handleRemovePost(postId: string) {
isRemoving.value = true
setTimeout(() => {
shouldHide.value = true
emit('removePost', postId)
}, 400) // Animation duration
}
</script>
<template>
<div
v-if="!shouldHide"
:class="[
'transition-all duration-400 ease-in-out',
isRemoving ? 'opacity-0 max-h-0 scale-y-0' : 'opacity-100 max-h-screen scale-y-100'
]"
>
<!-- Standard status content -->
<StatusContent :status="status" />
<!-- Corgi recommendation tag -->
<StatusRecommendationTag
:status="status"
@remove-post="handleRemovePost"
/>
</div>
</template>
4. Rich Recommendation System (StatusRecommendationTag.vue)
<script setup lang="ts">
const emit = defineEmits<{
removePost: [postId: string]
}>()
// Parse recommendation metadata
const parsedMetadata = computed(() => {
const metadata = status.recommendation_metadata
return typeof metadata === 'string' ? JSON.parse(metadata) : metadata
})
// Dynamic styling based on recommendation type
const reasonIcon = computed(() => {
const reasonCode = parsedMetadata.value.reason_code
switch (reasonCode) {
case 'TRENDING_TAG': return 'i-ri:fire-line'
case 'AUTHOR_INTERACTION': return 'i-ri:user-heart-line'
case 'SEMANTIC_MATCH': return 'i-ri:brain-line'
case 'HIGH_ENGAGEMENT': return 'i-ri:heart-pulse-line'
default: return 'i-ri:lightbulb-flash-line'
}
})
const tagColor = computed(() => {
const reasonCode = parsedMetadata.value.reason_code
switch (reasonCode) {
case 'TRENDING_TAG':
return 'bg-red-50 text-red-700 border-red-200'
case 'AUTHOR_INTERACTION':
return 'bg-pink-50 text-pink-700 border-pink-200'
case 'SEMANTIC_MATCH':
return 'bg-indigo-50 text-indigo-700 border-indigo-200'
default:
return 'bg-green-50 text-green-700 border-green-200'
}
})
async function handleFeedback(feedback: 'more' | 'less') {
isAnimating.value = feedback
await sendRecommendationFeedback(status.id, feedback)
if (feedback === 'less') {
isRemoving.value = true
setTimeout(() => {
emit('removePost', status.id)
}, 500)
}
}
</script>
<template>
<div
v-if="shouldShow"
:class="['recommendation-tag', tagColor]"
class="inline-flex items-center gap-1 px-2 py-1 text-xs rounded-full border"
>
<component :is="reasonIcon" class="w-3 h-3" />
<span>{{ parsedMetadata.reason_string }}</span>
<span v-if="showScores" :class="scoreBadgeColor">
{{ Math.round(parsedMetadata.score * 100) }}%
</span>
<!-- Feedback buttons -->
<div class="feedback-buttons">
<button @click="handleFeedback('more')" title="More like this">
<i-ri:thumb-up-line />
</button>
<button @click="handleFeedback('less')" title="Less like this">
<i-ri:thumb-down-line />
</button>
</div>
</div>
</template>
Implementation Steps
Step 1: Backend Configuration
Ensure your Corgi backend supports the seamless integration endpoints:
# Verify seamless endpoint
curl -X POST http://localhost:5002/api/v1/recommendations/seamless \
-H "Content-Type: application/json" \
-d '{
"timeline_type": "home",
"existing_statuses": [
{"id": "1", "content": "Hello world", "author": "user1"}
],
"max_recommendations": 3
}'
Expected Response:
[
{
"id": "rec_123",
"content": {
"id": "seamless_123",
"account": { "id": "author_id", "username": "author" },
"content": "Enhanced post content"
},
"score": 0.85,
"reason": "Similar to posts you liked",
"insertion_point": 6
}
]
Step 2: ELK Environment Setup
Configure ELK to enable seamless integration:
# .env configuration
NUXT_PUBLIC_CORGI_SEAMLESS=true
NUXT_PUBLIC_CORGI_API_URL=http://localhost:5002
NUXT_PUBLIC_CORGI_DEBUG=true # Development only
Step 3: User Detection Verification
Test the multi-method user detection:
// In browser console
const corgi = useCorgiSeamless()
console.log('Current user:', corgi.getCurrentUserId())
// Should detect user through multiple methods:
// - ELK global state
// - Nuxt app context
// - localStorage
// - URL parsing
// - Meta tags
Step 4: Timeline Enhancement Testing
# Start ELK development server
cd /Users/andrewnordstrom/Elk_Corgi/ELK
pnpm dev
# Open browser to localhost:5314
# Navigate to home timeline
# Look for enhanced posts with recommendation tags
Step 5: Feedback System Testing
- Positive Feedback: Click thumbs up on recommendations
- Negative Feedback: Click thumbs down to remove posts
- Preference Learning: Verify subsequent recommendations improve
Advanced Features
Smart User Detection
The integration uses 8 different methods to detect the current user:
// Method priorities (highest to lowest):
1. ELK global state ($elk.currentUser)
2. Nuxt app context ($nuxt.$currentUser)
3. localStorage user handle
4. localStorage accounts parsing
5. Settings key parsing
6. URL path extraction
7. Meta tag detection
8. Vue app instance state
Performance Optimization
// Recommendation caching
const recommendationCache = new Map<string, CorgiRecommendation[]>()
// Intelligent injection ratios
const injectionRatio = computed(() => {
const userEngagement = getUserEngagementRate()
return userEngagement > 0.7 ? 0.4 : 0.3 // More recs for engaged users
})
// Batch processing
const batchRecommendations = async (timelineChunks: Status[][]) => {
const batched = timelineChunks.map(chunk => enhanceTimeline('home', chunk))
return Promise.all(batched)
}
Privacy Features
// Pseudonymized user tracking
const userAlias = generateUserAlias(userId) // Never sends real user ID
// Consent management
const trackingConsent = localStorage.getItem('corgi-tracking-consent')
if (trackingConsent !== 'true') {
// Disable interaction tracking
}
// Local processing preference
const processLocally = userSettings.value.corgiProcessLocally
User Experience Flow
First-Time User
- Transparent Activation: User opens ELK normally
- Automatic Enhancement: Timeline gets enhanced without notification
- Gradual Discovery: User notices interesting content naturally
- Optional Feedback: User can engage with recommendation tags
Returning User
- Learned Preferences: Recommendations improve based on history
- Contextual Enhancement: Recommendations match current timeline context
- Preference Refinement: Continuous learning from user interactions
Troubleshooting
Common Issues
No Enhanced Content
# Check Corgi API health
curl http://localhost:5002/api/v1/health
# Verify user detection
# In browser console:
console.log(useCorgiSeamless().getCurrentUserId())
Timeline Not Loading
# Check ELK logs
tail -f ~/.nuxt/logs/elk.log
# Verify timeline endpoint
curl -X GET "http://localhost:5314/api/v1/timelines/home"
Recommendation Tags Not Showing
// Check status metadata
console.log(status.recommendation_metadata)
console.log(status.is_recommendation)
// Verify settings
console.log(localStorage.getItem('corgi-show-tags'))
Debug Mode
Enable comprehensive debugging:
# Environment variable
export NUXT_PUBLIC_CORGI_DEBUG=true
# Browser console filters
# [Corgi] - Main integration logs
# [Timeline] - Timeline enhancement logs
# [StatusCard] - Post display logs
Best Practices
Development
- Test Graceful Degradation: Disable Corgi API and verify ELK works
- Monitor Performance: Watch network requests and caching
- Validate Privacy: Ensure no sensitive data leaves the client
Production
- Silent Operation: Disable debug logs in production
- Error Handling: Comprehensive fallback mechanisms
- Performance Monitoring: Track enhancement success rates
User Experience
- Respect User Choices: Honor feedback immediately
- Maintain Transparency: Clear indication of AI content
- Preserve Privacy: Local processing when possible
Integration Testing
Unit Tests
// Test user detection
describe('getCurrentUserId', () => {
it('should detect user from ELK global state', () => {
window.$elk = { currentUser: { account: { acct: 'user@instance.com' } } }
expect(getCurrentUserId()).toBe('user@instance.com')
})
})
// Test timeline enhancement
describe('enhanceTimeline', () => {
it('should inject recommendations at correct intervals', async () => {
const timeline = createMockTimeline(10)
const enhanced = await enhanceTimeline('home', timeline)
expect(enhanced.length).toBeGreaterThan(10)
})
})
Integration Tests
# Test full flow
npm test e2e/seamless-integration.test.ts
# Test user interactions
npm test e2e/recommendation-feedback.test.ts
Monitoring and Analytics
Performance Metrics
// Track enhancement success rate
const enhancementMetrics = {
requests: 0,
successes: 0,
averageLatency: 0,
cacheHitRate: 0
}
// User engagement tracking
const engagementMetrics = {
recommendationClicks: 0,
positiveFeedback: 0,
negativeFeedback: 0,
timelineScrollDepth: 0
}
Health Monitoring
// Automatic health checks
setInterval(async () => {
const health = await checkCorgiHealth()
if (!health.ok) {
console.warn('[Corgi] API unhealthy, disabling enhancement')
temporarilyDisableEnhancement()
}
}, 30000) // Check every 30 seconds
Conclusion
The ELK + Corgi integration represents a sophisticated approach to AI-enhanced social media that prioritizes user experience, privacy, and transparency. By following this guide, you'll create a seamless experience where AI recommendations enhance timelines naturally without disrupting the core Mastodon experience.
The key principles that make this integration successful:
- Transparent Operation: Users benefit without knowing the complexity
- Privacy First: Local processing and consent-based tracking
- Graceful Degradation: Works perfectly even when AI is offline
- Rich Feedback: Comprehensive user control and preference learning
- Performance Optimized: Intelligent caching and batch processing
This integration showcases how AI can enhance social media experiences while maintaining the open, decentralized principles of the Fediverse.