wu981526092 commited on
Commit
45e0f20
·
1 Parent(s): 0ef9265

Add community templates feature with tabs in Models page - includes predefined templates and like functionality

Browse files
frontend/package-lock.json CHANGED
@@ -15,6 +15,7 @@
15
  "@radix-ui/react-slider": "^1.3.6",
16
  "@radix-ui/react-slot": "^1.2.3",
17
  "@radix-ui/react-switch": "^1.2.6",
 
18
  "@tailwindcss/typography": "^0.5.16",
19
  "ai": "^5.0.27",
20
  "class-variance-authority": "^0.7.1",
@@ -1320,6 +1321,37 @@
1320
  }
1321
  }
1322
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1323
  "node_modules/@radix-ui/react-select": {
1324
  "version": "2.2.6",
1325
  "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
@@ -1443,6 +1475,36 @@
1443
  }
1444
  }
1445
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1446
  "node_modules/@radix-ui/react-use-callback-ref": {
1447
  "version": "1.1.1",
1448
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
 
15
  "@radix-ui/react-slider": "^1.3.6",
16
  "@radix-ui/react-slot": "^1.2.3",
17
  "@radix-ui/react-switch": "^1.2.6",
18
+ "@radix-ui/react-tabs": "^1.1.13",
19
  "@tailwindcss/typography": "^0.5.16",
20
  "ai": "^5.0.27",
21
  "class-variance-authority": "^0.7.1",
 
1321
  }
1322
  }
1323
  },
1324
+ "node_modules/@radix-ui/react-roving-focus": {
1325
+ "version": "1.1.11",
1326
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
1327
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
1328
+ "license": "MIT",
1329
+ "dependencies": {
1330
+ "@radix-ui/primitive": "1.1.3",
1331
+ "@radix-ui/react-collection": "1.1.7",
1332
+ "@radix-ui/react-compose-refs": "1.1.2",
1333
+ "@radix-ui/react-context": "1.1.2",
1334
+ "@radix-ui/react-direction": "1.1.1",
1335
+ "@radix-ui/react-id": "1.1.1",
1336
+ "@radix-ui/react-primitive": "2.1.3",
1337
+ "@radix-ui/react-use-callback-ref": "1.1.1",
1338
+ "@radix-ui/react-use-controllable-state": "1.2.2"
1339
+ },
1340
+ "peerDependencies": {
1341
+ "@types/react": "*",
1342
+ "@types/react-dom": "*",
1343
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1344
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1345
+ },
1346
+ "peerDependenciesMeta": {
1347
+ "@types/react": {
1348
+ "optional": true
1349
+ },
1350
+ "@types/react-dom": {
1351
+ "optional": true
1352
+ }
1353
+ }
1354
+ },
1355
  "node_modules/@radix-ui/react-select": {
1356
  "version": "2.2.6",
1357
  "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
 
1475
  }
1476
  }
1477
  },
1478
+ "node_modules/@radix-ui/react-tabs": {
1479
+ "version": "1.1.13",
1480
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
1481
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
1482
+ "license": "MIT",
1483
+ "dependencies": {
1484
+ "@radix-ui/primitive": "1.1.3",
1485
+ "@radix-ui/react-context": "1.1.2",
1486
+ "@radix-ui/react-direction": "1.1.1",
1487
+ "@radix-ui/react-id": "1.1.1",
1488
+ "@radix-ui/react-presence": "1.1.5",
1489
+ "@radix-ui/react-primitive": "2.1.3",
1490
+ "@radix-ui/react-roving-focus": "1.1.11",
1491
+ "@radix-ui/react-use-controllable-state": "1.2.2"
1492
+ },
1493
+ "peerDependencies": {
1494
+ "@types/react": "*",
1495
+ "@types/react-dom": "*",
1496
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1497
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1498
+ },
1499
+ "peerDependenciesMeta": {
1500
+ "@types/react": {
1501
+ "optional": true
1502
+ },
1503
+ "@types/react-dom": {
1504
+ "optional": true
1505
+ }
1506
+ }
1507
+ },
1508
  "node_modules/@radix-ui/react-use-callback-ref": {
1509
  "version": "1.1.1",
1510
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
frontend/package.json CHANGED
@@ -17,6 +17,7 @@
17
  "@radix-ui/react-slider": "^1.3.6",
18
  "@radix-ui/react-slot": "^1.2.3",
19
  "@radix-ui/react-switch": "^1.2.6",
 
20
  "@tailwindcss/typography": "^0.5.16",
21
  "ai": "^5.0.27",
22
  "class-variance-authority": "^0.7.1",
 
17
  "@radix-ui/react-slider": "^1.3.6",
18
  "@radix-ui/react-slot": "^1.2.3",
19
  "@radix-ui/react-switch": "^1.2.6",
20
+ "@radix-ui/react-tabs": "^1.1.13",
21
  "@tailwindcss/typography": "^0.5.16",
22
  "ai": "^5.0.27",
23
  "class-variance-authority": "^0.7.1",
frontend/src/components/ui/tabs.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Tabs = TabsPrimitive.Root
7
+
8
+ const TabsList = React.forwardRef<
9
+ React.ElementRef<typeof TabsPrimitive.List>,
10
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
11
+ >(({ className, ...props }, ref) => (
12
+ <TabsPrimitive.List
13
+ ref={ref}
14
+ className={cn(
15
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ TabsList.displayName = TabsPrimitive.List.displayName
22
+
23
+ const TabsTrigger = React.forwardRef<
24
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
26
+ >(({ className, ...props }, ref) => (
27
+ <TabsPrimitive.Trigger
28
+ ref={ref}
29
+ className={cn(
30
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ ))
36
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37
+
38
+ const TabsContent = React.forwardRef<
39
+ React.ElementRef<typeof TabsPrimitive.Content>,
40
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
41
+ >(({ className, ...props }, ref) => (
42
+ <TabsPrimitive.Content
43
+ ref={ref}
44
+ className={cn(
45
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
46
+ className
47
+ )}
48
+ {...props}
49
+ />
50
+ ))
51
+ TabsContent.displayName = TabsPrimitive.Content.displayName
52
+
53
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
frontend/src/pages/Models.tsx CHANGED
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
2
  import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
3
  import { Button } from '@/components/ui/button'
4
  import { Badge } from '@/components/ui/badge'
 
5
  import {
6
  BookOpen,
7
  Brain,
@@ -14,7 +15,12 @@ import {
14
  Cloud,
15
  HardDrive,
16
  Bot,
17
- MessageSquare
 
 
 
 
 
18
  } from 'lucide-react'
19
 
20
  interface ModelInfo {
@@ -32,15 +38,122 @@ interface ModelsResponse {
32
  current_model: string
33
  }
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  export function Models() {
36
  const [models, setModels] = useState<ModelInfo[]>([])
37
  const [loading, setLoading] = useState(true)
38
  const [modelLoading, setModelLoading] = useState<string | null>(null)
39
  const [savedAssistants, setSavedAssistants] = useState<any[]>([])
 
 
40
 
41
  useEffect(() => {
42
  fetchModels()
43
  loadSavedAssistants()
 
44
  }, [])
45
 
46
  const loadSavedAssistants = () => {
@@ -66,6 +179,44 @@ export function Models() {
66
  localStorage.setItem('savedAssistants', JSON.stringify(updatedAssistants))
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  const fetchModels = async () => {
70
  try {
71
  const baseUrl = `${window.location.protocol}//${window.location.host}`
@@ -154,71 +305,109 @@ export function Models() {
154
  </div>
155
 
156
  <div className="flex-1 p-6">
157
- <div className="max-w-4xl mx-auto space-y-6">
158
 
159
  {/* Info Card */}
160
- <Card className="bg-blue-50 border-blue-200">
161
  <CardContent className="pt-6">
162
  <div className="flex items-start gap-3">
163
  <Info className="h-5 w-5 text-blue-600 mt-0.5" />
164
  <div>
165
- <h3 className="font-medium text-blue-900">Model Management</h3>
166
  <p className="text-sm text-blue-700 mt-1">
167
- Load models to use them in the playground. Models are cached locally for faster access.
168
- Each model requires significant storage space and initial download time.
169
  </p>
170
  </div>
171
  </div>
172
  </CardContent>
173
  </Card>
174
 
175
- {/* Custom Assistants */}
176
- {savedAssistants.length > 0 && (
177
- <div>
178
- <h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
179
- <Bot className="h-5 w-5" />
180
- My Custom Assistants
181
- </h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
183
- {savedAssistants.map((assistant) => (
184
- <Card key={assistant.id} className="hover:shadow-md transition-shadow">
185
- <CardHeader className="pb-3">
186
- <div className="flex items-start justify-between">
187
- <div className="flex-1">
188
- <CardTitle className="text-base">{assistant.name}</CardTitle>
189
- <p className="text-sm text-muted-foreground mt-1">
190
- {assistant.description || 'No description'}
191
- </p>
192
- </div>
193
- <Button
194
- variant="ghost"
195
- size="sm"
196
- onClick={() => deleteAssistant(assistant.id)}
197
- className="text-destructive hover:text-destructive"
198
- >
199
- <Trash2 className="h-4 w-4" />
200
- </Button>
201
- </div>
202
- </CardHeader>
203
- <CardContent className="space-y-3">
204
- <div className="text-xs space-y-1 text-muted-foreground">
205
- <div><span className="font-medium">Model:</span> {assistant.model}</div>
206
- <div><span className="font-medium">Temperature:</span> {assistant.temperature}</div>
207
- <div><span className="font-medium">Max Tokens:</span> {assistant.maxTokens}</div>
208
- <div><span className="font-medium">Created:</span> {new Date(assistant.createdAt).toLocaleDateString()}</div>
209
- </div>
210
- <div className="flex gap-2">
211
- <Button size="sm" onClick={() => loadAssistant(assistant)} className="flex-1">
212
- <MessageSquare className="h-4 w-4 mr-1" />
213
- Use
214
- </Button>
215
- </div>
216
- </CardContent>
217
- </Card>
218
  ))}
219
  </div>
220
- </div>
221
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
  {/* API Models Section */}
224
  <div>
@@ -292,6 +481,154 @@ export function Models() {
292
  )
293
  }
294
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  // ModelCard component for reusability
296
  interface ModelCardProps {
297
  model: ModelInfo
 
2
  import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
3
  import { Button } from '@/components/ui/button'
4
  import { Badge } from '@/components/ui/badge'
5
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
6
  import {
7
  BookOpen,
8
  Brain,
 
15
  Cloud,
16
  HardDrive,
17
  Bot,
18
+ MessageSquare,
19
+ Users,
20
+ Star,
21
+ Heart,
22
+ Share,
23
+ Copy
24
  } from 'lucide-react'
25
 
26
  interface ModelInfo {
 
38
  current_model: string
39
  }
40
 
41
+ // Community template interface
42
+ interface CommunityTemplate {
43
+ id: string
44
+ name: string
45
+ description: string
46
+ author: string
47
+ category: string
48
+ tags: string[]
49
+ model: string
50
+ systemPrompt: string
51
+ temperature: number
52
+ maxTokens: number
53
+ likes: number
54
+ downloads: number
55
+ isOfficial: boolean
56
+ createdAt: string
57
+ }
58
+
59
+ // Get predefined community templates
60
+ function getCommunityTemplates(): CommunityTemplate[] {
61
+ return [
62
+ {
63
+ id: 'code-reviewer',
64
+ name: 'Code Review Expert',
65
+ description: 'Professional code reviewer that provides detailed analysis, suggests improvements, and identifies potential issues.',
66
+ author: 'EdgeLLM Team',
67
+ category: 'coding',
68
+ tags: ['code review', 'programming', 'best practices'],
69
+ model: 'Qwen/Qwen3-30B-A3B',
70
+ systemPrompt: 'You are a senior software engineer specializing in code review. Analyze the provided code for:\n\n1. **Code Quality**: Structure, readability, maintainability\n2. **Best Practices**: Following language conventions and patterns\n3. **Performance**: Potential optimizations and bottlenecks\n4. **Security**: Common vulnerabilities and security issues\n5. **Testing**: Testability and edge cases\n\nProvide constructive feedback with specific examples and actionable suggestions.',
71
+ temperature: 0.3,
72
+ maxTokens: 1500,
73
+ likes: 245,
74
+ downloads: 1200,
75
+ isOfficial: true,
76
+ createdAt: '2024-01-15'
77
+ },
78
+ {
79
+ id: 'writing-tutor',
80
+ name: 'Academic Writing Tutor',
81
+ description: 'Helps improve academic writing with structure suggestions, grammar corrections, and clarity enhancements.',
82
+ author: 'Academic Guild',
83
+ category: 'writing',
84
+ tags: ['academic writing', 'essay', 'research'],
85
+ model: 'Qwen/Qwen3-30B-A3B',
86
+ systemPrompt: 'You are an experienced academic writing tutor. Help users improve their writing by:\n\n1. **Structure & Organization**: Clear thesis, logical flow, proper transitions\n2. **Clarity & Precision**: Eliminate ambiguity, improve word choice\n3. **Academic Style**: Formal tone, appropriate citations, scholarly voice\n4. **Grammar & Mechanics**: Correct errors, improve sentence variety\n5. **Argument Development**: Strengthen evidence, address counterarguments\n\nProvide specific feedback with examples and rewrite suggestions where helpful.',
87
+ temperature: 0.4,
88
+ maxTokens: 1200,
89
+ likes: 189,
90
+ downloads: 856,
91
+ isOfficial: false,
92
+ createdAt: '2024-01-20'
93
+ },
94
+ {
95
+ id: 'data-analyst',
96
+ name: 'Data Analysis Assistant',
97
+ description: 'Helps analyze data, create visualizations, and explain statistical concepts in simple terms.',
98
+ author: 'DataPro',
99
+ category: 'analysis',
100
+ tags: ['data science', 'statistics', 'visualization'],
101
+ model: 'Qwen/Qwen3-30B-A3B',
102
+ systemPrompt: 'You are a data analysis expert. Help users understand and analyze data by:\n\n1. **Data Exploration**: Identify patterns, outliers, relationships\n2. **Statistical Analysis**: Apply appropriate tests, interpret results\n3. **Visualization**: Suggest effective charts and graphs\n4. **Insights**: Draw meaningful conclusions from data\n5. **Communication**: Explain complex concepts simply\n\nProvide step-by-step analysis and practical recommendations.',
103
+ temperature: 0.2,
104
+ maxTokens: 1000,
105
+ likes: 156,
106
+ downloads: 643,
107
+ isOfficial: false,
108
+ createdAt: '2024-01-25'
109
+ },
110
+ {
111
+ id: 'creative-writer',
112
+ name: 'Creative Writing Coach',
113
+ description: 'Inspires creativity and helps develop compelling stories, characters, and narrative techniques.',
114
+ author: 'StoryMaster',
115
+ category: 'creative',
116
+ tags: ['creative writing', 'storytelling', 'fiction'],
117
+ model: 'Qwen/Qwen3-30B-A3B',
118
+ systemPrompt: 'You are a creative writing coach with expertise in storytelling. Help writers by:\n\n1. **Story Development**: Plot structure, pacing, conflict resolution\n2. **Character Creation**: Compelling personalities, realistic dialogue, character arcs\n3. **World Building**: Consistent settings, atmosphere, details\n4. **Writing Techniques**: Show vs tell, point of view, voice\n5. **Inspiration**: Creative prompts, overcoming writer\'s block\n\nProvide encouraging feedback and concrete suggestions to enhance creativity.',
119
+ temperature: 0.8,
120
+ maxTokens: 1200,
121
+ likes: 312,
122
+ downloads: 987,
123
+ isOfficial: false,
124
+ createdAt: '2024-01-30'
125
+ },
126
+ {
127
+ id: 'interview-prep',
128
+ name: 'Interview Preparation Coach',
129
+ description: 'Helps prepare for job interviews with practice questions, answer strategies, and confidence building.',
130
+ author: 'CareerBoost',
131
+ category: 'career',
132
+ tags: ['interview', 'job search', 'career'],
133
+ model: 'Qwen/Qwen3-30B-A3B',
134
+ systemPrompt: 'You are a professional career coach specializing in interview preparation. Help candidates by:\n\n1. **Question Practice**: Common and behavioral interview questions\n2. **Answer Framework**: STAR method, structured responses\n3. **Company Research**: Industry insights, company-specific preparation\n4. **Confidence Building**: Reducing anxiety, improving presentation\n5. **Follow-up**: Thank you notes, next steps\n\nProvide personalized advice and realistic practice scenarios.',
135
+ temperature: 0.5,
136
+ maxTokens: 1000,
137
+ likes: 278,
138
+ downloads: 1456,
139
+ isOfficial: true,
140
+ createdAt: '2024-02-05'
141
+ }
142
+ ]
143
+ }
144
+
145
  export function Models() {
146
  const [models, setModels] = useState<ModelInfo[]>([])
147
  const [loading, setLoading] = useState(true)
148
  const [modelLoading, setModelLoading] = useState<string | null>(null)
149
  const [savedAssistants, setSavedAssistants] = useState<any[]>([])
150
+ const [communityTemplates] = useState<CommunityTemplate[]>(getCommunityTemplates())
151
+ const [likedTemplates, setLikedTemplates] = useState<string[]>([])
152
 
153
  useEffect(() => {
154
  fetchModels()
155
  loadSavedAssistants()
156
+ loadLikedTemplates()
157
  }, [])
158
 
159
  const loadSavedAssistants = () => {
 
179
  localStorage.setItem('savedAssistants', JSON.stringify(updatedAssistants))
180
  }
181
 
182
+ const loadLikedTemplates = () => {
183
+ try {
184
+ const liked = JSON.parse(localStorage.getItem('likedTemplates') || '[]')
185
+ setLikedTemplates(liked)
186
+ } catch (error) {
187
+ console.error('Failed to load liked templates:', error)
188
+ }
189
+ }
190
+
191
+ const toggleLikeTemplate = (templateId: string) => {
192
+ const updatedLiked = likedTemplates.includes(templateId)
193
+ ? likedTemplates.filter(id => id !== templateId)
194
+ : [...likedTemplates, templateId]
195
+
196
+ setLikedTemplates(updatedLiked)
197
+ localStorage.setItem('likedTemplates', JSON.stringify(updatedLiked))
198
+ }
199
+
200
+ const useTemplate = (template: CommunityTemplate) => {
201
+ const assistantConfig = {
202
+ name: template.name,
203
+ description: template.description,
204
+ model: template.model,
205
+ systemPrompt: template.systemPrompt,
206
+ temperature: template.temperature,
207
+ maxTokens: template.maxTokens
208
+ }
209
+
210
+ localStorage.setItem('loadAssistantConfig', JSON.stringify(assistantConfig))
211
+ window.location.href = '/playground'
212
+ }
213
+
214
+ const shareMyAssistant = (assistant: any) => {
215
+ // In a real implementation, this would submit to a backend
216
+ // For now, we'll just show a success message
217
+ alert(`"${assistant.name}" has been shared to the community! (This is a demo - in production, it would be submitted for review.)`)
218
+ }
219
+
220
  const fetchModels = async () => {
221
  try {
222
  const baseUrl = `${window.location.protocol}//${window.location.host}`
 
305
  </div>
306
 
307
  <div className="flex-1 p-6">
308
+ <div className="max-w-6xl mx-auto">
309
 
310
  {/* Info Card */}
311
+ <Card className="bg-blue-50 border-blue-200 mb-6">
312
  <CardContent className="pt-6">
313
  <div className="flex items-start gap-3">
314
  <Info className="h-5 w-5 text-blue-600 mt-0.5" />
315
  <div>
316
+ <h3 className="font-medium text-blue-900">Assistant Library</h3>
317
  <p className="text-sm text-blue-700 mt-1">
318
+ Manage your custom assistants and discover community templates. Create specialized AI helpers for different tasks and workflows.
 
319
  </p>
320
  </div>
321
  </div>
322
  </CardContent>
323
  </Card>
324
 
325
+ {/* Tabs */}
326
+ <Tabs defaultValue="my-assistants" className="space-y-6">
327
+ <TabsList className="grid w-full grid-cols-4">
328
+ <TabsTrigger value="my-assistants" className="flex items-center gap-2">
329
+ <Bot className="h-4 w-4" />
330
+ My Assistants
331
+ </TabsTrigger>
332
+ <TabsTrigger value="community" className="flex items-center gap-2">
333
+ <Users className="h-4 w-4" />
334
+ Community
335
+ </TabsTrigger>
336
+ <TabsTrigger value="featured" className="flex items-center gap-2">
337
+ <Star className="h-4 w-4" />
338
+ Featured
339
+ </TabsTrigger>
340
+ <TabsTrigger value="models" className="flex items-center gap-2">
341
+ <Brain className="h-4 w-4" />
342
+ Models
343
+ </TabsTrigger>
344
+ </TabsList>
345
+
346
+ {/* My Assistants Tab */}
347
+ <TabsContent value="my-assistants" className="space-y-6">
348
+ {savedAssistants.length > 0 ? (
349
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
350
+ {savedAssistants.map((assistant) => (
351
+ <AssistantCard
352
+ key={assistant.id}
353
+ assistant={assistant}
354
+ onUse={() => loadAssistant(assistant)}
355
+ onDelete={() => deleteAssistant(assistant.id)}
356
+ onShare={() => shareMyAssistant(assistant)}
357
+ type="personal"
358
+ />
359
+ ))}
360
+ </div>
361
+ ) : (
362
+ <Card className="text-center p-8">
363
+ <Bot className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
364
+ <h3 className="text-lg font-medium mb-2">No assistants yet</h3>
365
+ <p className="text-muted-foreground mb-4">
366
+ Create your first assistant by configuring parameters in the Playground and saving it.
367
+ </p>
368
+ <Button onClick={() => window.location.href = '/playground'}>
369
+ Go to Playground
370
+ </Button>
371
+ </Card>
372
+ )}
373
+ </TabsContent>
374
+
375
+ {/* Community Templates Tab */}
376
+ <TabsContent value="community" className="space-y-6">
377
  <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
378
+ {communityTemplates.map((template) => (
379
+ <CommunityTemplateCard
380
+ key={template.id}
381
+ template={template}
382
+ isLiked={likedTemplates.includes(template.id)}
383
+ onLike={() => toggleLikeTemplate(template.id)}
384
+ onUse={() => useTemplate(template)}
385
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
  ))}
387
  </div>
388
+ </TabsContent>
389
+
390
+ {/* Featured Tab */}
391
+ <TabsContent value="featured" className="space-y-6">
392
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
393
+ {communityTemplates
394
+ .filter(t => t.isOfficial || t.likes > 200)
395
+ .map((template) => (
396
+ <CommunityTemplateCard
397
+ key={template.id}
398
+ template={template}
399
+ isLiked={likedTemplates.includes(template.id)}
400
+ onLike={() => toggleLikeTemplate(template.id)}
401
+ onUse={() => useTemplate(template)}
402
+ featured={true}
403
+ />
404
+ ))
405
+ }
406
+ </div>
407
+ </TabsContent>
408
+
409
+ {/* Models Tab */}
410
+ <TabsContent value="models" className="space-y-6">
411
 
412
  {/* API Models Section */}
413
  <div>
 
481
  )
482
  }
483
 
484
+ // Assistant Card Component for personal assistants
485
+ function AssistantCard({
486
+ assistant,
487
+ onUse,
488
+ onDelete,
489
+ onShare,
490
+ type = "personal"
491
+ }: {
492
+ assistant: any
493
+ onUse: () => void
494
+ onDelete: () => void
495
+ onShare: () => void
496
+ type?: "personal" | "community"
497
+ }) {
498
+ return (
499
+ <Card className="hover:shadow-md transition-shadow">
500
+ <CardHeader className="pb-3">
501
+ <div className="flex items-start justify-between">
502
+ <div className="flex-1">
503
+ <CardTitle className="text-base">{assistant.name}</CardTitle>
504
+ <p className="text-sm text-muted-foreground mt-1">
505
+ {assistant.description || 'No description'}
506
+ </p>
507
+ </div>
508
+ <div className="flex gap-1">
509
+ <Button
510
+ variant="ghost"
511
+ size="sm"
512
+ onClick={onShare}
513
+ className="text-blue-600 hover:text-blue-700"
514
+ title="Share to community"
515
+ >
516
+ <Share className="h-4 w-4" />
517
+ </Button>
518
+ <Button
519
+ variant="ghost"
520
+ size="sm"
521
+ onClick={onDelete}
522
+ className="text-destructive hover:text-destructive"
523
+ title="Delete assistant"
524
+ >
525
+ <Trash2 className="h-4 w-4" />
526
+ </Button>
527
+ </div>
528
+ </div>
529
+ </CardHeader>
530
+ <CardContent className="space-y-3">
531
+ <div className="text-xs space-y-1 text-muted-foreground">
532
+ <div><span className="font-medium">Model:</span> {assistant.model}</div>
533
+ <div><span className="font-medium">Temperature:</span> {assistant.temperature}</div>
534
+ <div><span className="font-medium">Max Tokens:</span> {assistant.maxTokens}</div>
535
+ <div><span className="font-medium">Created:</span> {new Date(assistant.createdAt).toLocaleDateString()}</div>
536
+ </div>
537
+ <Button size="sm" onClick={onUse} className="w-full">
538
+ <MessageSquare className="h-4 w-4 mr-2" />
539
+ Use Assistant
540
+ </Button>
541
+ </CardContent>
542
+ </Card>
543
+ )
544
+ }
545
+
546
+ // Community Template Card Component
547
+ function CommunityTemplateCard({
548
+ template,
549
+ isLiked,
550
+ onLike,
551
+ onUse,
552
+ featured = false
553
+ }: {
554
+ template: CommunityTemplate
555
+ isLiked: boolean
556
+ onLike: () => void
557
+ onUse: () => void
558
+ featured?: boolean
559
+ }) {
560
+ return (
561
+ <Card className={`hover:shadow-md transition-shadow ${featured ? 'ring-2 ring-yellow-200' : ''}`}>
562
+ <CardHeader className="pb-3">
563
+ <div className="flex items-start justify-between">
564
+ <div className="flex-1">
565
+ <div className="flex items-center gap-2 mb-1">
566
+ <CardTitle className="text-base">{template.name}</CardTitle>
567
+ {template.isOfficial && (
568
+ <Badge variant="default" className="text-xs px-2 py-0">
569
+ Official
570
+ </Badge>
571
+ )}
572
+ {featured && (
573
+ <Star className="h-4 w-4 text-yellow-500 fill-current" />
574
+ )}
575
+ </div>
576
+ <p className="text-xs text-muted-foreground">by {template.author}</p>
577
+ <p className="text-sm text-muted-foreground mt-1 line-clamp-2">
578
+ {template.description}
579
+ </p>
580
+ </div>
581
+ <Button
582
+ variant="ghost"
583
+ size="sm"
584
+ onClick={onLike}
585
+ className={isLiked ? "text-red-500" : "text-muted-foreground hover:text-red-500"}
586
+ title={isLiked ? "Unlike" : "Like"}
587
+ >
588
+ <Heart className={`h-4 w-4 ${isLiked ? 'fill-current' : ''}`} />
589
+ </Button>
590
+ </div>
591
+ </CardHeader>
592
+ <CardContent className="space-y-3">
593
+ <div className="flex flex-wrap gap-1">
594
+ {template.tags.slice(0, 3).map((tag) => (
595
+ <Badge key={tag} variant="secondary" className="text-xs">
596
+ {tag}
597
+ </Badge>
598
+ ))}
599
+ </div>
600
+
601
+ <div className="text-xs space-y-1 text-muted-foreground">
602
+ <div><span className="font-medium">Model:</span> {template.model}</div>
603
+ <div><span className="font-medium">Temperature:</span> {template.temperature}</div>
604
+ <div><span className="font-medium">Max Tokens:</span> {template.maxTokens}</div>
605
+ </div>
606
+
607
+ <div className="flex items-center justify-between text-xs text-muted-foreground">
608
+ <div className="flex items-center gap-3">
609
+ <div className="flex items-center gap-1">
610
+ <Heart className="h-3 w-3" />
611
+ <span>{template.likes}</span>
612
+ </div>
613
+ <div className="flex items-center gap-1">
614
+ <Download className="h-3 w-3" />
615
+ <span>{template.downloads}</span>
616
+ </div>
617
+ </div>
618
+ <span>{template.createdAt}</span>
619
+ </div>
620
+
621
+ <div className="flex gap-2">
622
+ <Button size="sm" onClick={onUse} className="flex-1">
623
+ <Copy className="h-4 w-4 mr-1" />
624
+ Use Template
625
+ </Button>
626
+ </div>
627
+ </CardContent>
628
+ </Card>
629
+ )
630
+ }
631
+
632
  // ModelCard component for reusability
633
  interface ModelCardProps {
634
  model: ModelInfo