wu981526092's picture
add
f479a01
raw
history blame
19.3 kB
import { useState, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Brain, MessageSquare, Search, TrendingUp, Crown, Bot, Eye, Star, Heart, Users, Award } from 'lucide-react'
// Community template interface
interface CommunityTemplate {
id: string
name: string
description: string
author: string
category: string
tags: string[]
model: string
systemPrompt: string
temperature: number
maxTokens: number
isOfficial: boolean
createdAt: string
icon: string
usageCount: number
rating: number
}
// Get all community templates
function getCommunityTemplates(): CommunityTemplate[] {
return [
{
id: 'code-reviewer',
name: 'Code Review Expert',
description: 'Professional code reviewer for detailed analysis and suggestions. Helps identify bugs, security issues, and performance optimizations.',
author: 'EdgeLLM Team',
category: 'Developer Tools',
tags: ['code review', 'programming', 'debugging'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a senior software engineer. Analyze code for quality, best practices, performance, and security. Provide constructive feedback with specific examples.',
temperature: 0.3,
maxTokens: 1000,
isOfficial: true,
createdAt: '2024-01-15',
icon: 'πŸ”',
usageCount: 1247,
rating: 4.8
},
{
id: 'writing-tutor',
name: 'Writing Assistant',
description: 'Helps improve writing with structure suggestions and clarity enhancements. Perfect for essays, articles, and professional documents.',
author: 'Community',
category: 'Writing',
tags: ['writing', 'editing', 'grammar'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a writing tutor. Help improve writing through structure, clarity, grammar, and style suggestions. Provide specific feedback and examples.',
temperature: 0.4,
maxTokens: 800,
isOfficial: false,
createdAt: '2024-01-20',
icon: 'πŸ“',
usageCount: 892,
rating: 4.6
},
{
id: 'creative-writer',
name: 'Creative Writing Coach',
description: 'Inspires creativity and helps develop compelling stories and characters. Great for fiction writers and storytellers.',
author: 'Community',
category: 'Writing',
tags: ['creative', 'storytelling', 'fiction'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a creative writing coach. Help develop stories, characters, and narrative techniques. Provide encouraging feedback and creative suggestions.',
temperature: 0.8,
maxTokens: 1000,
isOfficial: true,
createdAt: '2024-01-30',
icon: '✨',
usageCount: 634,
rating: 4.7
},
{
id: 'data-analyst',
name: 'Data Analysis Expert',
description: 'Helps analyze data patterns, create insights, and generate comprehensive reports with statistical analysis.',
author: 'EdgeLLM Team',
category: 'Data & Analytics',
tags: ['data', 'analytics', 'statistics'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a data analysis expert. Help users understand data patterns, create visualizations, and provide statistical insights.',
temperature: 0.2,
maxTokens: 1200,
isOfficial: true,
createdAt: '2024-01-18',
icon: 'πŸ“Š',
usageCount: 567,
rating: 4.9
},
{
id: 'language-tutor',
name: 'Language Learning Tutor',
description: 'Interactive language tutor for vocabulary, grammar, and conversation practice. Supports multiple languages.',
author: 'Community',
category: 'Education',
tags: ['language', 'learning', 'education'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a friendly language tutor. Help users learn languages through interactive exercises, explanations, and conversation practice.',
temperature: 0.6,
maxTokens: 800,
isOfficial: false,
createdAt: '2024-01-20',
icon: '🌐',
usageCount: 423,
rating: 4.5
},
{
id: 'business-advisor',
name: 'Business Strategy Advisor',
description: 'Provides strategic business advice, market analysis, and growth recommendations for entrepreneurs and businesses.',
author: 'EdgeLLM Team',
category: 'Business',
tags: ['strategy', 'business', 'consulting'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a business strategy consultant. Provide strategic advice, market insights, and growth recommendations based on business principles.',
temperature: 0.4,
maxTokens: 1000,
isOfficial: true,
createdAt: '2024-01-22',
icon: 'πŸ’Ό',
usageCount: 789,
rating: 4.7
},
{
id: 'research-assistant',
name: 'Research Assistant',
description: 'Helps with academic and professional research, source analysis, and comprehensive report writing.',
author: 'Community',
category: 'Research',
tags: ['research', 'academic', 'analysis'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a research assistant. Help users conduct thorough research, analyze sources, and organize findings into comprehensive reports.',
temperature: 0.3,
maxTokens: 1200,
isOfficial: false,
createdAt: '2024-01-25',
icon: 'πŸ“š',
usageCount: 356,
rating: 4.4
},
{
id: 'math-tutor',
name: 'Math & Science Tutor',
description: 'Expert tutor for mathematics, physics, chemistry, and other STEM subjects with step-by-step explanations.',
author: 'EdgeLLM Team',
category: 'Education',
tags: ['math', 'science', 'tutoring'],
model: 'Qwen/Qwen3-30B-A3B',
systemPrompt: 'You are a math and science tutor. Provide clear, step-by-step explanations for mathematical and scientific concepts.',
temperature: 0.2,
maxTokens: 1000,
isOfficial: true,
createdAt: '2024-01-28',
icon: 'πŸ”¬',
usageCount: 678,
rating: 4.8
}
]
}
export function Templates() {
const navigate = useNavigate()
const [isOnline, setIsOnline] = useState(false)
const [communityTemplates] = useState<CommunityTemplate[]>(getCommunityTemplates())
const [likedTemplates, setLikedTemplates] = useState<string[]>([])
const [searchQuery, setSearchQuery] = useState('')
const [selectedCategory, setSelectedCategory] = useState<string>('All')
useEffect(() => {
checkSystemStatus()
loadLikedTemplates()
}, [])
const checkSystemStatus = async () => {
try {
const baseUrl = `${window.location.protocol}//${window.location.host}`
const res = await fetch(`${baseUrl}/models`)
setIsOnline(res.ok)
} catch (error) {
setIsOnline(false)
}
}
const loadLikedTemplates = () => {
try {
const liked = JSON.parse(localStorage.getItem('likedTemplates') || '[]')
setLikedTemplates(liked)
} catch (error) {
console.error('Failed to load liked templates:', error)
}
}
const toggleLikeTemplate = (templateId: string) => {
const updatedLiked = likedTemplates.includes(templateId)
? likedTemplates.filter(id => id !== templateId)
: [...likedTemplates, templateId]
setLikedTemplates(updatedLiked)
localStorage.setItem('likedTemplates', JSON.stringify(updatedLiked))
}
const useTemplate = (template: CommunityTemplate) => {
const assistantConfig = {
name: template.name,
description: template.description,
model: template.model,
systemPrompt: template.systemPrompt,
temperature: template.temperature,
maxTokens: template.maxTokens
}
localStorage.setItem('loadAssistantConfig', JSON.stringify(assistantConfig))
navigate('/playground')
}
// Filter templates based on search and category
const filteredTemplates = communityTemplates.filter(template => {
const matchesSearch = template.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
template.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
const matchesCategory = selectedCategory === 'All' || template.category === selectedCategory
return matchesSearch && matchesCategory
})
const featuredTemplates = filteredTemplates.filter(t => t.isOfficial)
const trendingTemplates = [...filteredTemplates].sort((a, b) => b.usageCount - a.usageCount).slice(0, 6)
const categories = ['All', ...Array.from(new Set(communityTemplates.map(t => t.category)))]
return (
<div className="min-h-screen bg-background">
{/* Header */}
<div className="sticky top-0 z-40 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="max-w-6xl mx-auto px-6 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<Brain className="h-5 w-5 text-white" />
</div>
<h1 className="text-xl font-bold text-gray-900">Assistant Templates</h1>
<Badge variant={isOnline ? "default" : "secondary"} className="text-xs">
{isOnline ? 'Online' : 'Offline'}
</Badge>
</div>
</div>
<Button onClick={() => navigate('/playground')} className="flex items-center gap-2">
<Bot className="h-4 w-4" />
Create Assistant
</Button>
</div>
</div>
</div>
<div className="max-w-6xl mx-auto px-6 py-8">
{/* Hero Section */}
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
Explore AI Assistants
</h1>
<p className="text-lg text-gray-600 max-w-2xl mx-auto mb-8">
Discover, customize and deploy specialized AI assistants. Start with proven templates or build from scratch.
</p>
{/* Search Bar */}
<div className="relative max-w-2xl mx-auto mb-8">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 h-5 w-5" />
<Input
type="text"
placeholder="Search assistants..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-12 pr-4 py-3 text-lg rounded-full border-2 border-gray-200 focus:border-blue-500"
/>
</div>
{/* Category Filters */}
<div className="flex flex-wrap justify-center gap-2 mb-8">
{categories.map((category) => (
<Button
key={category}
variant={selectedCategory === category ? "default" : "outline"}
size="sm"
onClick={() => setSelectedCategory(category)}
className="rounded-full"
>
{category}
</Button>
))}
</div>
</div>
{/* Featured Section */}
{selectedCategory === 'All' && featuredTemplates.length > 0 && (
<div className="mb-12">
<div className="flex items-center gap-2 mb-6">
<Crown className="h-5 w-5 text-yellow-600" />
<h2 className="text-2xl font-bold text-gray-900">Featured</h2>
<Badge variant="secondary" className="text-xs">
Curated by EdgeLLM Team
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{featuredTemplates.slice(0, 3).map((template) => (
<TemplateCard
key={template.id}
template={template}
isLiked={likedTemplates.includes(template.id)}
onLike={() => toggleLikeTemplate(template.id)}
onUse={() => useTemplate(template)}
featured={true}
/>
))}
</div>
</div>
)}
{/* Trending Section */}
{selectedCategory === 'All' && (
<div className="mb-12">
<div className="flex items-center gap-2 mb-6">
<TrendingUp className="h-5 w-5 text-green-600" />
<h2 className="text-2xl font-bold text-gray-900">Trending</h2>
<Badge variant="secondary" className="text-xs">
Most popular this week
</Badge>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{trendingTemplates.slice(0, 6).map((template) => (
<TemplateCard
key={template.id}
template={template}
isLiked={likedTemplates.includes(template.id)}
onLike={() => toggleLikeTemplate(template.id)}
onUse={() => useTemplate(template)}
trending={true}
/>
))}
</div>
</div>
)}
{/* All Templates Section */}
<div className="mb-12">
<div className="flex items-center gap-2 mb-6">
<Users className="h-5 w-5 text-blue-600" />
<h2 className="text-2xl font-bold text-gray-900">
{selectedCategory === 'All' ? 'All Templates' : selectedCategory}
</h2>
<Badge variant="outline" className="text-xs">
{filteredTemplates.length} available
</Badge>
</div>
{filteredTemplates.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredTemplates.map((template) => (
<TemplateCard
key={template.id}
template={template}
isLiked={likedTemplates.includes(template.id)}
onLike={() => toggleLikeTemplate(template.id)}
onUse={() => useTemplate(template)}
/>
))}
</div>
) : (
<Card className="p-12 text-center">
<div className="flex flex-col items-center gap-4">
<Search className="h-12 w-12 text-gray-400" />
<div>
<h3 className="text-lg font-medium text-gray-900 mb-2">No templates found</h3>
<p className="text-gray-600">
Try adjusting your search or category filters
</p>
</div>
<Button variant="outline" onClick={() => {
setSearchQuery('')
setSelectedCategory('All')
}}>
Clear filters
</Button>
</div>
</Card>
)}
</div>
</div>
</div>
)
}
// Template Card Component
function TemplateCard({
template,
isLiked,
onLike,
onUse,
featured = false,
trending = false
}: {
template: CommunityTemplate
isLiked: boolean
onLike: () => void
onUse: () => void
featured?: boolean
trending?: boolean
}) {
return (
<Card className="group hover:shadow-xl transition-all duration-300 hover:-translate-y-1 border-2 hover:border-blue-200 relative">
{featured && (
<div className="absolute -top-2 -right-2 bg-yellow-500 text-white text-xs px-2 py-1 rounded-full font-medium flex items-center gap-1">
<Crown className="h-3 w-3" />
Featured
</div>
)}
{trending && (
<div className="absolute -top-2 -right-2 bg-green-500 text-white text-xs px-2 py-1 rounded-full font-medium flex items-center gap-1">
<TrendingUp className="h-3 w-3" />
Trending
</div>
)}
<CardHeader className="pb-4">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center text-2xl">
{template.icon}
</div>
<div>
<CardTitle className="text-lg group-hover:text-blue-600 transition-colors">
{template.name}
</CardTitle>
<div className="flex items-center gap-2 mt-1">
<p className="text-sm text-blue-600 font-medium">by {template.author}</p>
{template.isOfficial && (
<Badge variant="default" className="text-xs px-2 py-0 bg-blue-600">
<Award className="h-3 w-3 mr-1" />
Official
</Badge>
)}
</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={onLike}
className={`${isLiked ? "text-red-500" : "text-gray-400 hover:text-red-500"} transition-colors`}
title={isLiked ? "Unlike" : "Like"}
>
<Heart className={`h-4 w-4 ${isLiked ? 'fill-current' : ''}`} />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-gray-600 line-clamp-2 leading-relaxed">
{template.description}
</p>
{/* Tags */}
<div className="flex flex-wrap gap-2">
{template.tags.slice(0, 3).map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs bg-gray-100 hover:bg-gray-200 transition-colors">
{tag}
</Badge>
))}
</div>
{/* Stats */}
<div className="flex items-center justify-between text-sm text-gray-500">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Eye className="h-4 w-4" />
<span>{template.usageCount.toLocaleString()}</span>
</div>
<div className="flex items-center gap-1">
<Star className="h-4 w-4 text-yellow-500 fill-current" />
<span>{template.rating}</span>
</div>
</div>
<Badge variant="outline" className="text-xs">
{template.category}
</Badge>
</div>
{/* Action Button */}
<div className="pt-2">
<Button
onClick={onUse}
className="w-full group-hover:bg-blue-600 group-hover:text-white transition-all"
variant="outline"
>
<MessageSquare className="h-4 w-4 mr-2" />
Build Assistant
</Button>
</div>
</CardContent>
</Card>
)
}