File size: 13,926 Bytes
f479a01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4f5b21f
 
 
f479a01
4f5b21f
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
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
}

import { getTemplatesFromConfigs } from '@/config/assistants'

// Get all community templates from shared config
function getCommunityTemplates(): CommunityTemplate[] {
  return getTemplatesFromConfigs()
}

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>
  )
}