likhonsheikh commited on
Commit
d2be573
·
verified ·
1 Parent(s): 1c01f80

Upload components directory - minimal build

Browse files
Files changed (10) hide show
  1. code-editor.tsx +146 -0
  2. conversation-panel.tsx +222 -0
  3. model-selector.tsx +165 -0
  4. providers.tsx +18 -0
  5. ui/badge.tsx +36 -0
  6. ui/button.tsx +56 -0
  7. ui/card.tsx +79 -0
  8. ui/select.tsx +158 -0
  9. ui/tabs.tsx +53 -0
  10. ui/textarea.tsx +24 -0
code-editor.tsx ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+ import { Editor } from '@monaco-editor/react';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ interface CodeEditorProps {
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ className?: string;
11
+ language?: string;
12
+ theme?: string;
13
+ }
14
+
15
+ export function CodeEditor({
16
+ value,
17
+ onChange,
18
+ className,
19
+ language = 'javascript',
20
+ theme = 'vs-dark'
21
+ }: CodeEditorProps) {
22
+ const editorRef = useRef<any>(null);
23
+
24
+ const handleEditorDidMount = (editor: any) => {
25
+ editorRef.current = editor;
26
+
27
+ // Configure editor options
28
+ editor.updateOptions({
29
+ fontSize: 14,
30
+ fontFamily: 'JetBrains Mono, Monaco, Consolas, "Courier New", monospace',
31
+ minimap: { enabled: false },
32
+ scrollBeyondLastLine: false,
33
+ wordWrap: 'on',
34
+ automaticLayout: true,
35
+ tabSize: 2,
36
+ insertSpaces: true,
37
+ roundedSelection: false,
38
+ readOnly: false,
39
+ cursorStyle: 'line',
40
+ lineNumbers: 'on',
41
+ renderLineHighlight: 'line',
42
+ selectOnLineNumbers: true,
43
+ roundedSelection: false,
44
+ renderIndentGuides: true,
45
+ colorDecorators: true,
46
+ lineDecorationsWidth: 0,
47
+ lineNumbersMinChars: 3,
48
+ folding: true,
49
+ foldingHighlight: true,
50
+ showFoldingControls: 'mouseover',
51
+ smoothScrolling: true,
52
+ contextmenu: true,
53
+ mouseWheelZoom: true,
54
+ });
55
+
56
+ // Add custom key bindings
57
+ editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
58
+ // Handle save action
59
+ console.log('Save action triggered');
60
+ });
61
+
62
+ // Add code completion suggestions
63
+ const suggestions = [
64
+ {
65
+ label: 'console.log',
66
+ kind: monaco.languages.CompletionItemKind.Function,
67
+ insertText: 'console.log(${1:message});',
68
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
69
+ documentation: 'Log a message to the console'
70
+ },
71
+ {
72
+ label: 'function',
73
+ kind: monaco.languages.CompletionItemKind.Snippet,
74
+ insertText: 'function ${1:functionName}(${2:params}) {\n\t${3:// function body}\n}',
75
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
76
+ documentation: 'Create a function'
77
+ },
78
+ {
79
+ label: 'if',
80
+ kind: monaco.languages.CompletionItemKind.Snippet,
81
+ insertText: 'if (${1:condition}) {\n\t${2:// code}\n}',
82
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
83
+ documentation: 'Create an if statement'
84
+ }
85
+ ];
86
+
87
+ // Register completion provider
88
+ const completionProvider = {
89
+ provideCompletionItems: () => ({
90
+ suggestions: suggestions.map(suggestion => ({
91
+ ...suggestion,
92
+ range: undefined
93
+ }))
94
+ })
95
+ };
96
+
97
+ // Register the completion provider
98
+ const disposable = monaco.languages.registerCompletionItemProvider(language, completionProvider);
99
+
100
+ // Cleanup on unmount
101
+ return () => {
102
+ disposable.dispose();
103
+ };
104
+ };
105
+
106
+ const handleEditorChange = (value: string | undefined) => {
107
+ if (value !== undefined) {
108
+ onChange(value);
109
+ }
110
+ };
111
+
112
+ return (
113
+ <div className={cn("monaco-editor-container", className)}>
114
+ <Editor
115
+ height="100%"
116
+ defaultLanguage={language}
117
+ theme={theme}
118
+ value={value}
119
+ onChange={handleEditorChange}
120
+ onMount={handleEditorDidMount}
121
+ options={{
122
+ selectOnLineNumbers: true,
123
+ roundedSelection: false,
124
+ readOnly: false,
125
+ cursorStyle: 'line',
126
+ automaticLayout: true,
127
+ wordWrap: 'on',
128
+ minimap: { enabled: false },
129
+ scrollBeyondLastLine: false,
130
+ fontSize: 14,
131
+ fontFamily: 'JetBrains Mono, Monaco, Consolas, "Courier New", monospace',
132
+ tabSize: 2,
133
+ insertSpaces: true,
134
+ renderLineHighlight: 'line',
135
+ lineNumbers: 'on',
136
+ contextmenu: true,
137
+ mouseWheelZoom: true,
138
+ smoothScrolling: true,
139
+ folding: true,
140
+ foldingHighlight: true,
141
+ showFoldingControls: 'mouseover',
142
+ }}
143
+ />
144
+ </div>
145
+ );
146
+ }
conversation-panel.tsx ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { Card, CardContent } from '@/components/ui/card';
5
+ import { Badge } from '@/components/ui/badge';
6
+ import { Button } from '@/components/ui/button';
7
+ import { MessageSquare, Copy, ThumbsUp, ThumbsDown, RefreshCw } from 'lucide-react';
8
+ import { cn } from '@/lib/utils';
9
+
10
+ interface ConversationPanelProps {
11
+ completion: string;
12
+ isLoading: boolean;
13
+ className?: string;
14
+ }
15
+
16
+ interface Message {
17
+ id: string;
18
+ role: 'user' | 'assistant';
19
+ content: string;
20
+ timestamp: Date;
21
+ feedback?: 'positive' | 'negative';
22
+ }
23
+
24
+ export function ConversationPanel({ completion, isLoading, className }: ConversationPanelProps) {
25
+ const [messages, setMessages] = useState<Message[]>([]);
26
+ const [currentMessage, setCurrentMessage] = useState<string>('');
27
+
28
+ // Update messages when completion changes
29
+ useEffect(() => {
30
+ if (completion) {
31
+ const newMessage: Message = {
32
+ id: Date.now().toString(),
33
+ role: 'assistant',
34
+ content: completion,
35
+ timestamp: new Date(),
36
+ };
37
+ setMessages(prev => [...prev, newMessage]);
38
+ }
39
+ }, [completion]);
40
+
41
+ // Update current message during streaming
42
+ useEffect(() => {
43
+ if (isLoading && completion) {
44
+ setCurrentMessage(completion);
45
+ } else if (!isLoading) {
46
+ setCurrentMessage('');
47
+ }
48
+ }, [completion, isLoading]);
49
+
50
+ const handleCopy = (content: string) => {
51
+ navigator.clipboard.writeText(content);
52
+ };
53
+
54
+ const handleFeedback = (messageId: string, feedback: 'positive' | 'negative') => {
55
+ setMessages(prev => prev.map(msg =>
56
+ msg.id === messageId ? { ...msg, feedback } : msg
57
+ ));
58
+ };
59
+
60
+ const formatCode = (content: string) => {
61
+ // Simple code block formatting
62
+ const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g;
63
+ let formatted = content;
64
+ let match;
65
+
66
+ const parts: Array<{ type: 'text' | 'code'; content: string; language?: string }> = [];
67
+ let lastIndex = 0;
68
+
69
+ while ((match = codeBlockRegex.exec(content)) !== null) {
70
+ if (match.index > lastIndex) {
71
+ parts.push({ type: 'text', content: content.slice(lastIndex, match.index) });
72
+ }
73
+ parts.push({
74
+ type: 'code',
75
+ content: match[2].trim(),
76
+ language: match[1] || 'javascript'
77
+ });
78
+ lastIndex = match.index + match[0].length;
79
+ }
80
+
81
+ if (lastIndex < content.length) {
82
+ parts.push({ type: 'text', content: content.slice(lastIndex) });
83
+ }
84
+
85
+ return parts.length > 0 ? parts : [{ type: 'text', content }];
86
+ };
87
+
88
+ return (
89
+ <div className={cn("flex flex-col h-full", className)}>
90
+ {/* Messages */}
91
+ <div className="flex-1 overflow-y-auto space-y-4 mb-4 max-h-[400px] code-area">
92
+ {messages.length === 0 && !isLoading && (
93
+ <div className="flex items-center justify-center h-full text-muted-foreground">
94
+ <div className="text-center">
95
+ <MessageSquare className="w-12 h-12 mx-auto mb-4 opacity-50" />
96
+ <p className="text-lg font-medium mb-2">Start a conversation</p>
97
+ <p className="text-sm">
98
+ Ask me to help with your code, generate functions, debug issues, or discuss programming concepts.
99
+ </p>
100
+ </div>
101
+ </div>
102
+ )}
103
+
104
+ {messages.map((message) => (
105
+ <Card key={message.id} className="relative">
106
+ <CardContent className="p-4">
107
+ <div className="flex items-start justify-between mb-3">
108
+ <div className="flex items-center gap-2">
109
+ <Badge
110
+ variant={message.role === 'assistant' ? 'default' : 'secondary'}
111
+ className="text-xs"
112
+ >
113
+ {message.role === 'assistant' ? 'AI Assistant' : 'You'}
114
+ </Badge>
115
+ <span className="text-xs text-muted-foreground">
116
+ {message.timestamp.toLocaleTimeString()}
117
+ </span>
118
+ </div>
119
+
120
+ <div className="flex items-center gap-1">
121
+ <Button
122
+ variant="ghost"
123
+ size="sm"
124
+ onClick={() => handleCopy(message.content)}
125
+ className="h-6 w-6 p-0"
126
+ >
127
+ <Copy className="w-3 h-3" />
128
+ </Button>
129
+
130
+ {message.role === 'assistant' && (
131
+ <>
132
+ <Button
133
+ variant="ghost"
134
+ size="sm"
135
+ onClick={() => handleFeedback(message.id, 'positive')}
136
+ className={cn(
137
+ "h-6 w-6 p-0",
138
+ message.feedback === 'positive' && "text-green-600"
139
+ )}
140
+ >
141
+ <ThumbsUp className="w-3 h-3" />
142
+ </Button>
143
+
144
+ <Button
145
+ variant="ghost"
146
+ size="sm"
147
+ onClick={() => handleFeedback(message.id, 'negative')}
148
+ className={cn(
149
+ "h-6 w-6 p-0",
150
+ message.feedback === 'negative' && "text-red-600"
151
+ )}
152
+ >
153
+ <ThumbsDown className="w-3 h-3" />
154
+ </Button>
155
+ </>
156
+ )}
157
+ </div>
158
+ </div>
159
+
160
+ <div className="prose prose-sm max-w-none">
161
+ {formatCode(message.content).map((part, index) => (
162
+ <div key={index}>
163
+ {part.type === 'code' ? (
164
+ <div className="relative">
165
+ <Badge variant="outline" className="text-xs mb-2">
166
+ {part.language}
167
+ </Badge>
168
+ <pre className="bg-muted p-3 rounded-md overflow-x-auto text-sm">
169
+ <code>{part.content}</code>
170
+ </pre>
171
+ </div>
172
+ ) : (
173
+ <div className="whitespace-pre-wrap text-sm leading-relaxed">
174
+ {part.content}
175
+ </div>
176
+ )}
177
+ </div>
178
+ ))}
179
+ </div>
180
+ </CardContent>
181
+ </Card>
182
+ ))}
183
+
184
+ {/* Current streaming message */}
185
+ {isLoading && currentMessage && (
186
+ <Card className="relative border-dashed">
187
+ <CardContent className="p-4">
188
+ <div className="flex items-center gap-2 mb-3">
189
+ <Badge variant="default" className="text-xs">
190
+ <RefreshCw className="w-3 h-3 mr-1 animate-spin" />
191
+ AI Assistant
192
+ </Badge>
193
+ <span className="text-xs text-muted-foreground">Thinking...</span>
194
+ </div>
195
+
196
+ <div className="prose prose-sm max-w-none">
197
+ {formatCode(currentMessage).map((part, index) => (
198
+ <div key={index}>
199
+ {part.type === 'code' ? (
200
+ <div className="relative">
201
+ <Badge variant="outline" className="text-xs mb-2">
202
+ {part.language}
203
+ </Badge>
204
+ <pre className="bg-muted p-3 rounded-md overflow-x-auto text-sm opacity-75">
205
+ <code>{part.content}</code>
206
+ </pre>
207
+ </div>
208
+ ) : (
209
+ <div className="whitespace-pre-wrap text-sm leading-relaxed opacity-75">
210
+ {part.content}
211
+ </div>
212
+ )}
213
+ </div>
214
+ ))}
215
+ </div>
216
+ </CardContent>
217
+ </Card>
218
+ )}
219
+ </div>
220
+ </div>
221
+ );
222
+ }
model-selector.tsx ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
5
+ import { Badge } from '@/components/ui/badge';
6
+ import { Settings, Cpu, Zap, Brain } from 'lucide-react';
7
+
8
+ interface ModelSelectorProps {
9
+ selectedModel: string;
10
+ onModelChange: (model: string) => void;
11
+ models: string[];
12
+ }
13
+
14
+ const modelIcons: Record<string, React.ReactNode> = {
15
+ 'deepseek-ai': <Brain className="w-3 h-3" />,
16
+ 'Qwen': <Cpu className="w-3 h-3" />,
17
+ 'moonshotai': <Zap className="w-3 h-3" />,
18
+ 'zai-org': <Settings className="w-3 h-3" />,
19
+ 'MiniMaxAI': <Brain className="w-3 h-3" />,
20
+ 'meta-llama': <Cpu className="w-3 h-3" />,
21
+ 'google': <Brain className="w-3 h-3" />,
22
+ };
23
+
24
+ const getProviderFromModel = (modelName: string): string => {
25
+ if (modelName.includes('deepseek-ai') || modelName.includes('Qwen') ||
26
+ modelName.includes('moonshotai') || modelName.includes('zai-org') ||
27
+ modelName.includes('MiniMaxAI') || modelName.includes('meta-llama') ||
28
+ modelName.includes('google')) {
29
+ return 'Hugging Face';
30
+ }
31
+ return 'Unknown';
32
+ };
33
+
34
+ const getModelCategory = (modelName: string): string => {
35
+ if (modelName.includes('DeepSeek')) return 'Reasoning';
36
+ if (modelName.includes('Coder')) return 'Code';
37
+ if (modelName.includes('VL')) return 'Vision';
38
+ if (modelName.includes('Kimi') && modelName.includes('Thinking')) return 'Reasoning';
39
+ if (modelName.includes('Kimi')) return 'General';
40
+ if (modelName.includes('GLM')) return 'General';
41
+ return 'Language';
42
+ };
43
+
44
+ const getDisplayName = (modelName: string): string => {
45
+ const parts = modelName.split('/');
46
+ const model = parts[1] || modelName;
47
+
48
+ // Clean up model names for better display
49
+ const cleanName = model
50
+ .replace('-0324', '')
51
+ .replace('-0528', '')
52
+ .replace('-0905', '')
53
+ .replace('-Instruct', '')
54
+ .replace('-A22B', '')
55
+ .replace('-A35B', '')
56
+ .replace('-VL-7B', '')
57
+ .replace('-Exp', '')
58
+ .replace('-Distill-', '-')
59
+ .replace('-Terminus', '')
60
+ .replace('DeepSeek-V3-0324', 'DeepSeek V3')
61
+ .replace('DeepSeek-R1-0528', 'DeepSeek R1')
62
+ .replace('DeepSeek-V3.1', 'DeepSeek V3.1')
63
+ .replace('DeepSeek-V3.1-Terminus', 'DeepSeek V3.1 Terminus')
64
+ .replace('DeepSeek-V3.2-Exp', 'DeepSeek V3.2 Exp')
65
+ .replace('Qwen3-Coder-480B-A35B-Instruct', 'Qwen3 Coder 480B')
66
+ .replace('Qwen2.5-VL-7B-Instruct', 'Qwen2.5 VL 7B')
67
+ .replace('Kimi-K2-Instruct', 'Kimi K2')
68
+ .replace('Kimi-K2-Instruct-0905', 'Kimi K2 0905')
69
+ .replace('Kimi-K2-Thinking', 'Kimi K2 Thinking')
70
+ .replace('GLM-4.6', 'GLM-4.6')
71
+ .replace('MiniMax-M2', 'MiniMax M2')
72
+ .replace('Llama-3.1-8B-Instruct', 'Llama 3.1 8B')
73
+ .replace('Llama-3.1-70B-Instruct', 'Llama 3.1 70B')
74
+ .replace('Llama-3.3-70B-Instruct', 'Llama 3.3 70B')
75
+ .replace('Llama-4-Scout-17B-16E-Instruct', 'Llama 4 Scout 17B')
76
+ .replace('gemma-3-27b-it', 'Gemma 3 27B');
77
+
78
+ return cleanName;
79
+ };
80
+
81
+ export function ModelSelector({ selectedModel, onModelChange, models }: ModelSelectorProps) {
82
+ const [open, setOpen] = useState(false);
83
+
84
+ const getIconForModel = (modelName: string) => {
85
+ for (const [key, icon] of Object.entries(modelIcons)) {
86
+ if (modelName.includes(key)) {
87
+ return icon;
88
+ }
89
+ }
90
+ return <Brain className="w-3 h-3" />;
91
+ };
92
+
93
+ const groupModelsByProvider = () => {
94
+ const groups: Record<string, string[]> = {};
95
+
96
+ models.forEach(model => {
97
+ const provider = getProviderFromModel(model);
98
+ if (!groups[provider]) {
99
+ groups[provider] = [];
100
+ }
101
+ groups[provider].push(model);
102
+ });
103
+
104
+ return groups;
105
+ };
106
+
107
+ const groupedModels = groupModelsByProvider();
108
+
109
+ return (
110
+ <Select
111
+ value={selectedModel}
112
+ onValueChange={onModelChange}
113
+ onOpenChange={setOpen}
114
+ >
115
+ <SelectTrigger className="w-[280px]">
116
+ <div className="flex items-center gap-2 truncate">
117
+ {getIconForModel(selectedModel)}
118
+ <div className="flex-1 min-w-0">
119
+ <div className="flex items-center gap-2">
120
+ <span className="font-medium truncate">
121
+ {getDisplayName(selectedModel)}
122
+ </span>
123
+ <Badge variant="outline" className="text-xs">
124
+ {getModelCategory(selectedModel)}
125
+ </Badge>
126
+ </div>
127
+ <div className="text-xs text-muted-foreground truncate">
128
+ {getProviderFromModel(selectedModel)}
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </SelectTrigger>
133
+
134
+ <SelectContent>
135
+ {Object.entries(groupedModels).map(([provider, providerModels]) => (
136
+ <div key={provider}>
137
+ <div className="px-2 py-1.5 text-xs font-semibold text-muted-foreground border-b">
138
+ {provider}
139
+ </div>
140
+ {providerModels.map((model) => (
141
+ <SelectItem key={model} value={model}>
142
+ <div className="flex items-center gap-2 w-full">
143
+ {getIconForModel(model)}
144
+ <div className="flex-1 min-w-0">
145
+ <div className="flex items-center gap-2">
146
+ <span className="font-medium truncate">
147
+ {getDisplayName(model)}
148
+ </span>
149
+ <Badge variant="outline" className="text-xs flex-shrink-0">
150
+ {getModelCategory(model)}
151
+ </Badge>
152
+ </div>
153
+ <div className="text-xs text-muted-foreground truncate">
154
+ {model}
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </SelectItem>
159
+ ))}
160
+ </div>
161
+ ))}
162
+ </SelectContent>
163
+ </Select>
164
+ );
165
+ }
providers.tsx ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { ReactNode } from 'react';
4
+
5
+ interface ProvidersProps {
6
+ children: ReactNode;
7
+ }
8
+
9
+ export function Providers({ children }: ProvidersProps) {
10
+ return (
11
+ <div>
12
+ {children}
13
+ </div>
14
+ );
15
+ }
16
+
17
+ // Temporary simple provider until we fix AI SDK dependencies
18
+ export const supportedModels = {};
ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
ui/button.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLDivElement,
49
+ React.HTMLAttributes<HTMLDivElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
ui/select.tsx ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as SelectPrimitive from "@radix-ui/react-select"
3
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ const SelectGroup = SelectPrimitive.Group
10
+
11
+ const SelectValue = SelectPrimitive.Value
12
+
13
+ const SelectTrigger = React.forwardRef<
14
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
15
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
16
+ >(({ className, children, ...props }, ref) => (
17
+ <SelectPrimitive.Trigger
18
+ ref={ref}
19
+ className={cn(
20
+ "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ <SelectPrimitive.Icon asChild>
27
+ <ChevronDown className="h-4 w-4 opacity-50" />
28
+ </SelectPrimitive.Icon>
29
+ </SelectPrimitive.Trigger>
30
+ ))
31
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32
+
33
+ const SelectScrollUpButton = React.forwardRef<
34
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
35
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
36
+ >(({ className, ...props }, ref) => (
37
+ <SelectPrimitive.ScrollUpButton
38
+ ref={ref}
39
+ className={cn(
40
+ "flex cursor-default items-center justify-center py-1",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ <ChevronUp className="h-4 w-4" />
46
+ </SelectPrimitive.ScrollUpButton>
47
+ ))
48
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
49
+
50
+ const SelectScrollDownButton = React.forwardRef<
51
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
52
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
53
+ >(({ className, ...props }, ref) => (
54
+ <SelectPrimitive.ScrollDownButton
55
+ ref={ref}
56
+ className={cn(
57
+ "flex cursor-default items-center justify-center py-1",
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ <ChevronDown className="h-4 w-4" />
63
+ </SelectPrimitive.ScrollDownButton>
64
+ ))
65
+ SelectScrollDownButton.displayName =
66
+ SelectPrimitive.ScrollDownButton.displayName
67
+
68
+ const SelectContent = React.forwardRef<
69
+ React.ElementRef<typeof SelectPrimitive.Content>,
70
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
71
+ >(({ className, children, position = "popper", ...props }, ref) => (
72
+ <SelectPrimitive.Portal>
73
+ <SelectPrimitive.Content
74
+ ref={ref}
75
+ className={cn(
76
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
77
+ position === "popper" &&
78
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
79
+ className
80
+ )}
81
+ position={position}
82
+ {...props}
83
+ >
84
+ <SelectScrollUpButton />
85
+ <SelectPrimitive.Viewport
86
+ className={cn(
87
+ "p-1",
88
+ position === "popper" &&
89
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
90
+ )}
91
+ >
92
+ {children}
93
+ </SelectPrimitive.Viewport>
94
+ <SelectScrollDownButton />
95
+ </SelectPrimitive.Content>
96
+ </SelectPrimitive.Portal>
97
+ ))
98
+ SelectContent.displayName = SelectPrimitive.Content.displayName
99
+
100
+ const SelectLabel = React.forwardRef<
101
+ React.ElementRef<typeof SelectPrimitive.Label>,
102
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
103
+ >(({ className, ...props }, ref) => (
104
+ <SelectPrimitive.Label
105
+ ref={ref}
106
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
107
+ {...props}
108
+ />
109
+ ))
110
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
111
+
112
+ const SelectItem = React.forwardRef<
113
+ React.ElementRef<typeof SelectPrimitive.Item>,
114
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
115
+ >(({ className, children, ...props }, ref) => (
116
+ <SelectPrimitive.Item
117
+ ref={ref}
118
+ className={cn(
119
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
120
+ className
121
+ )}
122
+ {...props}
123
+ >
124
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
125
+ <SelectPrimitive.ItemIndicator>
126
+ <Check className="h-4 w-4" />
127
+ </SelectPrimitive.ItemIndicator>
128
+ </span>
129
+
130
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
131
+ </SelectPrimitive.Item>
132
+ ))
133
+ SelectItem.displayName = SelectPrimitive.Item.displayName
134
+
135
+ const SelectSeparator = React.forwardRef<
136
+ React.ElementRef<typeof SelectPrimitive.Separator>,
137
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
138
+ >(({ className, ...props }, ref) => (
139
+ <SelectPrimitive.Separator
140
+ ref={ref}
141
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
142
+ {...props}
143
+ />
144
+ ))
145
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
146
+
147
+ export {
148
+ Select,
149
+ SelectGroup,
150
+ SelectValue,
151
+ SelectTrigger,
152
+ SelectContent,
153
+ SelectLabel,
154
+ SelectItem,
155
+ SelectSeparator,
156
+ SelectScrollUpButton,
157
+ SelectScrollDownButton,
158
+ }
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 }
ui/textarea.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface TextareaProps
6
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
7
+
8
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
9
+ ({ className, ...props }, ref) => {
10
+ return (
11
+ <textarea
12
+ className={cn(
13
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
14
+ className
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+ )
22
+ Textarea.displayName = "Textarea"
23
+
24
+ export { Textarea }