File size: 19,282 Bytes
f479a01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
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>
  )
}