mukaddamzaid commited on
Commit
8c61b89
·
1 Parent(s): 51105d0

feat: add back grok 3 mini and API key management functionality

Browse files

- Integrated the XAI provider into the application, allowing for model interactions.
- Implemented an API Key Manager component for managing API keys for OpenAI, Anthropic, Groq, and XAI.
- Updated the Chat Sidebar to include an option for accessing the API Key Manager.
- Enhanced the providers.ts file to utilize the new XAI client and manage API keys securely.

ai/providers.ts CHANGED
@@ -1,8 +1,14 @@
1
- import { openai } from "@ai-sdk/openai";
2
- import { google } from "@ai-sdk/google";
3
- import { groq } from "@ai-sdk/groq";
4
- import { customProvider, wrapLanguageModel, extractReasoningMiddleware } from "ai";
5
- import { anthropic } from "@ai-sdk/anthropic";
 
 
 
 
 
 
6
  export interface ModelInfo {
7
  provider: string;
8
  name: string;
@@ -15,26 +21,51 @@ const middleware = extractReasoningMiddleware({
15
  tagName: 'think',
16
  });
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  const languageModels = {
19
- "gemini-2.5-flash": google("gemini-2.5-flash-preview-04-17"),
20
- "gpt-4.1-mini": openai("gpt-4.1-mini"),
21
- "claude-3-7-sonnet": anthropic('claude-3-7-sonnet-20250219'),
22
  "qwen-qwq": wrapLanguageModel(
23
  {
24
- model: groq("qwen-qwq-32b"),
25
  middleware
26
  }
27
  ),
 
28
  };
29
 
30
  export const modelDetails: Record<keyof typeof languageModels, ModelInfo> = {
31
- "gemini-2.5-flash": {
32
- provider: "Google",
33
- name: "Gemini 2.5 Flash",
34
- description: "Latest version of Google's Gemini 2.5 Flash with strong reasoning and coding capabilities.",
35
- apiVersion: "gemini-2.5-flash-preview-04-17",
36
- capabilities: ["Balance", "Efficient", "Agentic"]
37
- },
38
  "gpt-4.1-mini": {
39
  provider: "OpenAI",
40
  name: "GPT-4.1 Mini",
@@ -56,8 +87,25 @@ export const modelDetails: Record<keyof typeof languageModels, ModelInfo> = {
56
  apiVersion: "qwen-qwq",
57
  capabilities: ["Reasoning", "Efficient", "Agentic"]
58
  },
 
 
 
 
 
 
 
59
  };
60
 
 
 
 
 
 
 
 
 
 
 
61
  export const model = customProvider({
62
  languageModels,
63
  });
 
1
+ import { createOpenAI } from "@ai-sdk/openai";
2
+ import { createGroq } from "@ai-sdk/groq";
3
+ import { createAnthropic } from "@ai-sdk/anthropic";
4
+ import { createXai } from "@ai-sdk/xai";
5
+
6
+ import {
7
+ customProvider,
8
+ wrapLanguageModel,
9
+ extractReasoningMiddleware
10
+ } from "ai";
11
+
12
  export interface ModelInfo {
13
  provider: string;
14
  name: string;
 
21
  tagName: 'think',
22
  });
23
 
24
+ // Helper to get API keys from environment variables first, then localStorage
25
+ const getApiKey = (key: string): string | undefined => {
26
+ // Check for environment variables first
27
+ if (process.env[key]) {
28
+ return process.env[key] || undefined;
29
+ }
30
+
31
+ // Fall back to localStorage if available
32
+ if (typeof window !== 'undefined') {
33
+ return window.localStorage.getItem(key) || undefined;
34
+ }
35
+
36
+ return undefined;
37
+ };
38
+
39
+ // Create provider instances with API keys from localStorage
40
+ const openaiClient = createOpenAI({
41
+ apiKey: getApiKey('OPENAI_API_KEY'),
42
+ });
43
+
44
+ const anthropicClient = createAnthropic({
45
+ apiKey: getApiKey('ANTHROPIC_API_KEY'),
46
+ });
47
+
48
+ const groqClient = createGroq({
49
+ apiKey: getApiKey('GROQ_API_KEY'),
50
+ });
51
+
52
+ const xaiClient = createXai({
53
+ apiKey: getApiKey('XAI_API_KEY'),
54
+ });
55
+
56
  const languageModels = {
57
+ "gpt-4.1-mini": openaiClient("gpt-4.1-mini"),
58
+ "claude-3-7-sonnet": anthropicClient('claude-3-7-sonnet-20250219'),
 
59
  "qwen-qwq": wrapLanguageModel(
60
  {
61
+ model: groqClient("qwen-qwq-32b"),
62
  middleware
63
  }
64
  ),
65
+ "grok-3-mini": xaiClient("grok-3-mini-latest"),
66
  };
67
 
68
  export const modelDetails: Record<keyof typeof languageModels, ModelInfo> = {
 
 
 
 
 
 
 
69
  "gpt-4.1-mini": {
70
  provider: "OpenAI",
71
  name: "GPT-4.1 Mini",
 
87
  apiVersion: "qwen-qwq",
88
  capabilities: ["Reasoning", "Efficient", "Agentic"]
89
  },
90
+ "grok-3-mini": {
91
+ provider: "XAI",
92
+ name: "Grok 3 Mini",
93
+ description: "Latest version of XAI's Grok 3 Mini with strong reasoning and coding capabilities.",
94
+ apiVersion: "grok-3-mini-latest",
95
+ capabilities: ["Reasoning", "Efficient", "Agentic"]
96
+ },
97
  };
98
 
99
+ // Update API keys when localStorage changes (for runtime updates)
100
+ if (typeof window !== 'undefined') {
101
+ window.addEventListener('storage', (event) => {
102
+ // Reload the page if any API key changed to refresh the providers
103
+ if (event.key?.includes('API_KEY')) {
104
+ window.location.reload();
105
+ }
106
+ });
107
+ }
108
+
109
  export const model = customProvider({
110
  languageModels,
111
  });
components/api-key-manager.tsx ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from "react";
2
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Label } from "@/components/ui/label";
6
+ import { toast } from "sonner";
7
+
8
+ // API key configuration
9
+ interface ApiKeyConfig {
10
+ name: string;
11
+ key: string;
12
+ storageKey: string;
13
+ label: string;
14
+ placeholder: string;
15
+ }
16
+
17
+ // Available API keys configuration
18
+ const API_KEYS_CONFIG: ApiKeyConfig[] = [
19
+ {
20
+ name: "OpenAI",
21
+ key: "openai",
22
+ storageKey: "OPENAI_API_KEY",
23
+ label: "OpenAI API Key",
24
+ placeholder: "sk-..."
25
+ },
26
+ {
27
+ name: "Anthropic",
28
+ key: "anthropic",
29
+ storageKey: "ANTHROPIC_API_KEY",
30
+ label: "Anthropic API Key",
31
+ placeholder: "sk-ant-..."
32
+ },
33
+ {
34
+ name: "Groq",
35
+ key: "groq",
36
+ storageKey: "GROQ_API_KEY",
37
+ label: "Groq API Key",
38
+ placeholder: "gsk_..."
39
+ },
40
+ {
41
+ name: "XAI",
42
+ key: "xai",
43
+ storageKey: "XAI_API_KEY",
44
+ label: "XAI API Key",
45
+ placeholder: "xai-..."
46
+ }
47
+ ];
48
+
49
+ interface ApiKeyManagerProps {
50
+ open: boolean;
51
+ onOpenChange: (open: boolean) => void;
52
+ }
53
+
54
+ export function ApiKeyManager({ open, onOpenChange }: ApiKeyManagerProps) {
55
+ // State to store API keys
56
+ const [apiKeys, setApiKeys] = useState<Record<string, string>>({});
57
+
58
+ // Load API keys from localStorage on initial mount
59
+ useEffect(() => {
60
+ const storedKeys: Record<string, string> = {};
61
+
62
+ API_KEYS_CONFIG.forEach(config => {
63
+ const value = localStorage.getItem(config.storageKey);
64
+ if (value) {
65
+ storedKeys[config.key] = value;
66
+ }
67
+ });
68
+
69
+ setApiKeys(storedKeys);
70
+ }, []);
71
+
72
+ // Update API key in state
73
+ const handleApiKeyChange = (key: string, value: string) => {
74
+ setApiKeys(prev => ({
75
+ ...prev,
76
+ [key]: value
77
+ }));
78
+ };
79
+
80
+ // Save API keys to localStorage
81
+ const handleSaveApiKeys = () => {
82
+ try {
83
+ API_KEYS_CONFIG.forEach(config => {
84
+ const value = apiKeys[config.key];
85
+
86
+ if (value && value.trim()) {
87
+ localStorage.setItem(config.storageKey, value.trim());
88
+ } else {
89
+ localStorage.removeItem(config.storageKey);
90
+ }
91
+ });
92
+
93
+ toast.success("API keys saved successfully");
94
+ onOpenChange(false);
95
+ } catch (error) {
96
+ console.error("Error saving API keys:", error);
97
+ toast.error("Failed to save API keys");
98
+ }
99
+ };
100
+
101
+ // Clear all API keys
102
+ const handleClearApiKeys = () => {
103
+ try {
104
+ API_KEYS_CONFIG.forEach(config => {
105
+ localStorage.removeItem(config.storageKey);
106
+ });
107
+
108
+ setApiKeys({});
109
+ toast.success("All API keys cleared");
110
+ } catch (error) {
111
+ console.error("Error clearing API keys:", error);
112
+ toast.error("Failed to clear API keys");
113
+ }
114
+ };
115
+
116
+ return (
117
+ <Dialog open={open} onOpenChange={onOpenChange}>
118
+ <DialogContent className="sm:max-w-[500px]">
119
+ <DialogHeader>
120
+ <DialogTitle>API Key Settings</DialogTitle>
121
+ <DialogDescription>
122
+ Enter your own API keys for different AI providers. Keys are stored securely in your browser&apos;s local storage.
123
+ </DialogDescription>
124
+ </DialogHeader>
125
+
126
+ <div className="grid gap-4 py-4">
127
+ {API_KEYS_CONFIG.map(config => (
128
+ <div key={config.key} className="grid gap-2">
129
+ <Label htmlFor={config.key}>{config.label}</Label>
130
+ <Input
131
+ id={config.key}
132
+ type="password"
133
+ value={apiKeys[config.key] || ""}
134
+ onChange={(e) => handleApiKeyChange(config.key, e.target.value)}
135
+ placeholder={config.placeholder}
136
+ />
137
+ </div>
138
+ ))}
139
+ </div>
140
+
141
+ <DialogFooter className="flex justify-between sm:justify-between">
142
+ <Button
143
+ variant="destructive"
144
+ onClick={handleClearApiKeys}
145
+ >
146
+ Clear All Keys
147
+ </Button>
148
+ <div className="flex gap-2">
149
+ <Button
150
+ variant="outline"
151
+ onClick={() => onOpenChange(false)}
152
+ >
153
+ Cancel
154
+ </Button>
155
+ <Button onClick={handleSaveApiKeys}>
156
+ Save Keys
157
+ </Button>
158
+ </div>
159
+ </DialogFooter>
160
+ </DialogContent>
161
+ </Dialog>
162
+ );
163
+ }
components/chat-sidebar.tsx CHANGED
@@ -2,7 +2,7 @@
2
 
3
  import { useState, useEffect } from "react";
4
  import { useRouter, usePathname } from "next/navigation";
5
- import { MessageSquare, PlusCircle, Trash2, ServerIcon, Settings, Sparkles, ChevronsUpDown, Copy, Pencil, Github } from "lucide-react";
6
  import {
7
  Sidebar,
8
  SidebarContent,
@@ -23,6 +23,7 @@ import { Badge } from "@/components/ui/badge";
23
  import { toast } from "sonner";
24
  import Image from "next/image";
25
  import { MCPServerManager } from "./mcp-server-manager";
 
26
  import { ThemeToggle } from "./theme-toggle";
27
  import { getUserId, updateUserId } from "@/lib/user-id";
28
  import { useChats } from "@/lib/hooks/use-chats";
@@ -57,6 +58,7 @@ export function ChatSidebar() {
57
  const pathname = usePathname();
58
  const [userId, setUserId] = useState<string>('');
59
  const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
 
60
  const { state } = useSidebar();
61
  const isCollapsed = state === "collapsed";
62
  const [editUserIdOpen, setEditUserIdOpen] = useState(false);
@@ -384,6 +386,13 @@ export function ChatSidebar() {
384
  <Settings className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
385
  MCP Settings
386
  </DropdownMenuItem>
 
 
 
 
 
 
 
387
  <DropdownMenuItem onSelect={(e) => {
388
  e.preventDefault();
389
  window.open("https://git.new/s-mcp", "_blank");
@@ -413,6 +422,11 @@ export function ChatSidebar() {
413
  open={mcpSettingsOpen}
414
  onOpenChange={setMcpSettingsOpen}
415
  />
 
 
 
 
 
416
  </SidebarFooter>
417
 
418
  <Dialog open={editUserIdOpen} onOpenChange={(open) => {
 
2
 
3
  import { useState, useEffect } from "react";
4
  import { useRouter, usePathname } from "next/navigation";
5
+ import { MessageSquare, PlusCircle, Trash2, ServerIcon, Settings, Sparkles, ChevronsUpDown, Copy, Pencil, Github, Key } from "lucide-react";
6
  import {
7
  Sidebar,
8
  SidebarContent,
 
23
  import { toast } from "sonner";
24
  import Image from "next/image";
25
  import { MCPServerManager } from "./mcp-server-manager";
26
+ import { ApiKeyManager } from "./api-key-manager";
27
  import { ThemeToggle } from "./theme-toggle";
28
  import { getUserId, updateUserId } from "@/lib/user-id";
29
  import { useChats } from "@/lib/hooks/use-chats";
 
58
  const pathname = usePathname();
59
  const [userId, setUserId] = useState<string>('');
60
  const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
61
+ const [apiKeySettingsOpen, setApiKeySettingsOpen] = useState(false);
62
  const { state } = useSidebar();
63
  const isCollapsed = state === "collapsed";
64
  const [editUserIdOpen, setEditUserIdOpen] = useState(false);
 
386
  <Settings className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
387
  MCP Settings
388
  </DropdownMenuItem>
389
+ <DropdownMenuItem onSelect={(e) => {
390
+ e.preventDefault();
391
+ setApiKeySettingsOpen(true);
392
+ }}>
393
+ <Key className="mr-2 h-4 w-4 hover:text-sidebar-accent" />
394
+ API Keys
395
+ </DropdownMenuItem>
396
  <DropdownMenuItem onSelect={(e) => {
397
  e.preventDefault();
398
  window.open("https://git.new/s-mcp", "_blank");
 
422
  open={mcpSettingsOpen}
423
  onOpenChange={setMcpSettingsOpen}
424
  />
425
+
426
+ <ApiKeyManager
427
+ open={apiKeySettingsOpen}
428
+ onOpenChange={setApiKeySettingsOpen}
429
+ />
430
  </SidebarFooter>
431
 
432
  <Dialog open={editUserIdOpen} onOpenChange={(open) => {
components/model-picker.tsx CHANGED
@@ -35,14 +35,14 @@ export const ModelPicker = ({ selectedModel, setSelectedModel }: ModelPickerProp
35
  const getProviderIcon = (provider: string) => {
36
  switch (provider.toLowerCase()) {
37
  case 'anthropic':
38
- return <Sparkles className="h-3 w-3 text-yellow-500" />;
39
  case 'openai':
40
  return <Zap className="h-3 w-3 text-green-500" />;
41
  case 'google':
42
  return <Zap className="h-3 w-3 text-red-500" />;
43
  case 'groq':
44
  return <Sparkles className="h-3 w-3 text-blue-500" />;
45
- case 'cohere':
46
  return <Sparkles className="h-3 w-3 text-yellow-500" />;
47
  default:
48
  return <Info className="h-3 w-3 text-blue-500" />;
 
35
  const getProviderIcon = (provider: string) => {
36
  switch (provider.toLowerCase()) {
37
  case 'anthropic':
38
+ return <Sparkles className="h-3 w-3 text-orange-600" />;
39
  case 'openai':
40
  return <Zap className="h-3 w-3 text-green-500" />;
41
  case 'google':
42
  return <Zap className="h-3 w-3 text-red-500" />;
43
  case 'groq':
44
  return <Sparkles className="h-3 w-3 text-blue-500" />;
45
+ case 'xai':
46
  return <Sparkles className="h-3 w-3 text-yellow-500" />;
47
  default:
48
  return <Info className="h-3 w-3 text-blue-500" />;
package.json CHANGED
@@ -19,6 +19,7 @@
19
  "@ai-sdk/groq": "^1.2.8",
20
  "@ai-sdk/openai": "^1.3.16",
21
  "@ai-sdk/react": "^1.2.9",
 
22
  "@neondatabase/serverless": "^1.0.0",
23
  "@radix-ui/react-accordion": "^1.2.7",
24
  "@radix-ui/react-avatar": "^1.1.6",
 
19
  "@ai-sdk/groq": "^1.2.8",
20
  "@ai-sdk/openai": "^1.3.16",
21
  "@ai-sdk/react": "^1.2.9",
22
+ "@ai-sdk/xai": "^1.2.14",
23
  "@neondatabase/serverless": "^1.0.0",
24
  "@radix-ui/react-accordion": "^1.2.7",
25
  "@radix-ui/react-avatar": "^1.1.6",
pnpm-lock.yaml CHANGED
@@ -26,6 +26,9 @@ importers:
26
  '@ai-sdk/react':
27
  specifier: ^1.2.9
28
 
 
 
29
  '@neondatabase/serverless':
30
  specifier: ^1.0.0
31
  version: 1.0.0
@@ -207,6 +210,12 @@ packages:
207
  peerDependencies:
208
  zod: ^3.0.0
209
 
 
 
 
 
 
 
210
  '@ai-sdk/[email protected]':
211
  resolution: {integrity: sha512-pjtiBKt1GgaSKZryTbM3tqgoegJwgAUlp1+X5uN6T+VPnI4FLSymV65tyloWzDlyqZmi9HXnnSRPu76VoL5D5g==}
212
  engines: {node: '>=18'}
@@ -239,6 +248,12 @@ packages:
239
  peerDependencies:
240
  zod: ^3.23.8
241
 
 
 
 
 
 
 
242
  '@alloc/[email protected]':
243
  resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
244
  engines: {node: '>=10'}
@@ -3698,6 +3713,12 @@ snapshots:
3698
  '@ai-sdk/provider-utils': 2.2.7([email protected])
3699
  zod: 3.24.2
3700
 
 
 
 
 
 
 
3701
3702
  dependencies:
3703
  '@ai-sdk/provider': 1.1.3
@@ -3732,6 +3753,13 @@ snapshots:
3732
  zod: 3.24.2
3733
  zod-to-json-schema: 3.24.5([email protected])
3734
 
 
 
 
 
 
 
 
3735
  '@alloc/[email protected]': {}
3736
 
3737
  '@babel/[email protected]':
 
26
  '@ai-sdk/react':
27
  specifier: ^1.2.9
28
29
+ '@ai-sdk/xai':
30
+ specifier: ^1.2.14
31
+ version: 1.2.14([email protected])
32
  '@neondatabase/serverless':
33
  specifier: ^1.0.0
34
  version: 1.0.0
 
210
  peerDependencies:
211
  zod: ^3.0.0
212
 
213
+ '@ai-sdk/[email protected]':
214
+ resolution: {integrity: sha512-WGk3hTYMOkExgKqBZPWgow/2lp8JLYeJiiO/T/SxBa1NolxNcV+fPaZVvIyqeHGV0/AIXHqNtqI95CFblM47iA==}
215
+ engines: {node: '>=18'}
216
+ peerDependencies:
217
+ zod: ^3.0.0
218
+
219
  '@ai-sdk/[email protected]':
220
  resolution: {integrity: sha512-pjtiBKt1GgaSKZryTbM3tqgoegJwgAUlp1+X5uN6T+VPnI4FLSymV65tyloWzDlyqZmi9HXnnSRPu76VoL5D5g==}
221
  engines: {node: '>=18'}
 
248
  peerDependencies:
249
  zod: ^3.23.8
250
 
251
+ '@ai-sdk/[email protected]':
252
+ resolution: {integrity: sha512-ZxGjI/D1heIOX6XqyAwrqyBSkICP2mge2La7qHmr5RRcqw9+vWU+2cAg2y1FqCw8FpN/pGFZI81Dm473cYwlpg==}
253
+ engines: {node: '>=18'}
254
+ peerDependencies:
255
+ zod: ^3.0.0
256
+
257
  '@alloc/[email protected]':
258
  resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
259
  engines: {node: '>=10'}
 
3713
  '@ai-sdk/provider-utils': 2.2.7([email protected])
3714
  zod: 3.24.2
3715
 
3716
3717
+ dependencies:
3718
+ '@ai-sdk/provider': 1.1.3
3719
+ '@ai-sdk/provider-utils': 2.2.7([email protected])
3720
+ zod: 3.24.2
3721
+
3722
3723
  dependencies:
3724
  '@ai-sdk/provider': 1.1.3
 
3753
  zod: 3.24.2
3754
  zod-to-json-schema: 3.24.5([email protected])
3755
 
3756
3757
+ dependencies:
3758
+ '@ai-sdk/openai-compatible': 0.2.12([email protected])
3759
+ '@ai-sdk/provider': 1.1.3
3760
+ '@ai-sdk/provider-utils': 2.2.7([email protected])
3761
+ zod: 3.24.2
3762
+
3763
  '@alloc/[email protected]': {}
3764
 
3765
  '@babel/[email protected]':