apteva 0.2.11 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,222 @@
1
+ import React, { useState, useEffect, createContext, useContext, type ReactNode } from "react";
2
+ import { Chat } from "@apteva/apteva-kit";
3
+ import { useAuth } from "../../context";
4
+
5
+ interface MetaAgentStatus {
6
+ enabled: boolean;
7
+ available?: boolean;
8
+ reason?: string;
9
+ agent?: {
10
+ id: string;
11
+ name: string;
12
+ status: "stopped" | "running";
13
+ port: number | null;
14
+ provider: string;
15
+ model: string;
16
+ };
17
+ }
18
+
19
+ interface MetaAgentContextValue {
20
+ status: MetaAgentStatus | null;
21
+ isOpen: boolean;
22
+ isStarting: boolean;
23
+ error: string | null;
24
+ isAvailable: boolean;
25
+ isRunning: boolean;
26
+ agent: MetaAgentStatus["agent"] | undefined;
27
+ toggle: () => void;
28
+ close: () => void;
29
+ startAgent: () => Promise<void>;
30
+ }
31
+
32
+ const MetaAgentContext = createContext<MetaAgentContextValue | null>(null);
33
+
34
+ export function useMetaAgent() {
35
+ return useContext(MetaAgentContext);
36
+ }
37
+
38
+ export function MetaAgentProvider({ children }: { children: ReactNode }) {
39
+ const { authFetch, isAuthenticated, isLoading: authLoading } = useAuth();
40
+ const [status, setStatus] = useState<MetaAgentStatus | null>(null);
41
+ const [isOpen, setIsOpen] = useState(false);
42
+ const [isStarting, setIsStarting] = useState(false);
43
+ const [error, setError] = useState<string | null>(null);
44
+
45
+ // Fetch meta agent status
46
+ const fetchStatus = async () => {
47
+ try {
48
+ const res = await authFetch("/api/meta-agent/status");
49
+ const data = await res.json();
50
+ setStatus(data);
51
+ } catch (e) {
52
+ console.error("[MetaAgent] Failed to fetch status:", e);
53
+ }
54
+ };
55
+
56
+ useEffect(() => {
57
+ // Only fetch when authenticated
58
+ if (!authLoading && isAuthenticated) {
59
+ fetchStatus();
60
+ }
61
+ }, [authFetch, isAuthenticated, authLoading]);
62
+
63
+ // Start the meta agent
64
+ const startAgent = async () => {
65
+ setIsStarting(true);
66
+ setError(null);
67
+ try {
68
+ const res = await authFetch("/api/meta-agent/start", { method: "POST" });
69
+ const data = await res.json();
70
+ if (!res.ok) {
71
+ setError(data.error || "Failed to start assistant");
72
+ } else {
73
+ await fetchStatus();
74
+ }
75
+ } catch (e) {
76
+ setError("Failed to start assistant");
77
+ }
78
+ setIsStarting(false);
79
+ };
80
+
81
+ const isAvailable = !!(status?.enabled && status?.available);
82
+ const agent = status?.agent;
83
+ const isRunning = !!(agent?.status === "running" && agent?.port);
84
+
85
+ const value: MetaAgentContextValue = {
86
+ status,
87
+ isOpen,
88
+ isStarting,
89
+ error,
90
+ isAvailable,
91
+ isRunning,
92
+ agent,
93
+ toggle: () => setIsOpen(!isOpen),
94
+ close: () => setIsOpen(false),
95
+ startAgent,
96
+ };
97
+
98
+ return (
99
+ <MetaAgentContext.Provider value={value}>
100
+ {children}
101
+ </MetaAgentContext.Provider>
102
+ );
103
+ }
104
+
105
+ // Header button component - to be used in Header.tsx
106
+ export function MetaAgentButton() {
107
+ const ctx = useMetaAgent();
108
+ if (!ctx?.isAvailable) return null;
109
+
110
+ return (
111
+ <button
112
+ onClick={ctx.toggle}
113
+ className={`hidden md:flex items-center gap-2 px-3 py-2 rounded transition ${
114
+ ctx.isOpen
115
+ ? "bg-[#f97316] text-white"
116
+ : "bg-[#151515] hover:bg-[#1a1a1a] text-[#888] hover:text-white"
117
+ }`}
118
+ title="Apteva Assistant"
119
+ >
120
+ <AssistantIcon className="w-5 h-5" />
121
+ <span className="text-sm">Assistant</span>
122
+ {ctx.isRunning && (
123
+ <span className="w-2 h-2 rounded-full bg-green-400" />
124
+ )}
125
+ </button>
126
+ );
127
+ }
128
+
129
+ // Chat panel component - renders as a right-side drawer
130
+ export function MetaAgentPanel() {
131
+ const ctx = useMetaAgent();
132
+ if (!ctx?.isAvailable || !ctx.isOpen) return null;
133
+
134
+ const { agent, isRunning, error, isStarting, startAgent, close } = ctx;
135
+
136
+ return (
137
+ <>
138
+ {/* Backdrop */}
139
+ <div
140
+ className="hidden md:block fixed inset-0 bg-black/20 z-40"
141
+ onClick={close}
142
+ />
143
+
144
+ {/* Drawer Panel */}
145
+ <div className="hidden md:flex fixed top-0 right-0 h-full w-[480px] lg:w-[540px] bg-[#0a0a0a] border-l border-[#1a1a1a] shadow-2xl z-50 flex-col">
146
+ {/* Header */}
147
+ <div className="flex items-center justify-between px-4 py-3 border-b border-[#1a1a1a] bg-[#111]">
148
+ <div className="flex items-center gap-2">
149
+ <div className={`w-2 h-2 rounded-full ${isRunning ? "bg-green-400" : "bg-[#444]"}`} />
150
+ <span className="font-medium text-sm">Apteva Assistant</span>
151
+ </div>
152
+ <button
153
+ onClick={close}
154
+ className="text-[#666] hover:text-[#888] transition"
155
+ >
156
+ <CloseIcon />
157
+ </button>
158
+ </div>
159
+
160
+ {/* Content */}
161
+ <div className="flex-1 min-h-0 flex flex-col">
162
+ {isRunning ? (
163
+ <Chat
164
+ agentId="default"
165
+ apiUrl={`/api/agents/${agent!.id}`}
166
+ placeholder="Ask me anything about Apteva..."
167
+ variant="terminal"
168
+ showHeader={false}
169
+ />
170
+ ) : (
171
+ <div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
172
+ <AssistantIcon className="w-12 h-12 text-[#333] mb-4" />
173
+ <h3 className="font-medium mb-2">Apteva Assistant</h3>
174
+ <p className="text-sm text-[#666] mb-6">
175
+ I can help you navigate Apteva, create agents, set up MCP servers, and more.
176
+ </p>
177
+ {error && (
178
+ <p className="text-sm text-red-400 mb-4">{error}</p>
179
+ )}
180
+ <button
181
+ onClick={startAgent}
182
+ disabled={isStarting}
183
+ className="bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-white px-6 py-2 rounded font-medium transition"
184
+ >
185
+ {isStarting ? "Starting..." : "Start Assistant"}
186
+ </button>
187
+ </div>
188
+ )}
189
+ </div>
190
+ </div>
191
+ </>
192
+ );
193
+ }
194
+
195
+ function AssistantIcon({ className = "w-6 h-6" }: { className?: string }) {
196
+ return (
197
+ <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
198
+ <path
199
+ strokeLinecap="round"
200
+ strokeLinejoin="round"
201
+ strokeWidth={2}
202
+ d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
203
+ />
204
+ </svg>
205
+ );
206
+ }
207
+
208
+ function CloseIcon() {
209
+ return (
210
+ <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
211
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
212
+ </svg>
213
+ );
214
+ }
215
+
216
+ function MinimizeIcon() {
217
+ return (
218
+ <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
219
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
220
+ </svg>
221
+ );
222
+ }
@@ -7,41 +7,56 @@ import type { Provider } from "../../types";
7
7
  type SettingsTab = "providers" | "projects" | "updates" | "data";
8
8
 
9
9
  export function SettingsPage() {
10
+ const { projectsEnabled } = useProjects();
10
11
  const [activeTab, setActiveTab] = useState<SettingsTab>("providers");
11
12
 
13
+ const tabs: { key: SettingsTab; label: string }[] = [
14
+ { key: "providers", label: "Providers" },
15
+ ...(projectsEnabled ? [{ key: "projects" as SettingsTab, label: "Projects" }] : []),
16
+ { key: "updates", label: "Updates" },
17
+ { key: "data", label: "Data" },
18
+ ];
19
+
12
20
  return (
13
- <div className="flex-1 flex overflow-hidden">
14
- {/* Settings Sidebar */}
15
- <div className="w-48 border-r border-[#1a1a1a] p-4">
21
+ <div className="flex-1 flex flex-col md:flex-row overflow-hidden">
22
+ {/* Mobile: Horizontal scrolling tabs */}
23
+ <div className="md:hidden border-b border-[#1a1a1a] bg-[#0a0a0a]">
24
+ <div className="flex overflow-x-auto" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>
25
+ {tabs.map(tab => (
26
+ <button
27
+ key={tab.key}
28
+ onClick={() => setActiveTab(tab.key)}
29
+ className={`flex-shrink-0 px-4 py-3 text-sm font-medium border-b-2 transition ${
30
+ activeTab === tab.key
31
+ ? "border-[#f97316] text-[#f97316]"
32
+ : "border-transparent text-[#666] hover:text-[#888]"
33
+ }`}
34
+ >
35
+ {tab.label}
36
+ </button>
37
+ ))}
38
+ </div>
39
+ </div>
40
+
41
+ {/* Desktop: Settings Sidebar */}
42
+ <div className="hidden md:block w-48 border-r border-[#1a1a1a] p-4 flex-shrink-0">
16
43
  <h2 className="text-sm font-medium text-[#666] uppercase tracking-wider mb-3">Settings</h2>
17
44
  <nav className="space-y-1">
18
- <SettingsNavItem
19
- label="Providers"
20
- active={activeTab === "providers"}
21
- onClick={() => setActiveTab("providers")}
22
- />
23
- <SettingsNavItem
24
- label="Projects"
25
- active={activeTab === "projects"}
26
- onClick={() => setActiveTab("projects")}
27
- />
28
- <SettingsNavItem
29
- label="Updates"
30
- active={activeTab === "updates"}
31
- onClick={() => setActiveTab("updates")}
32
- />
33
- <SettingsNavItem
34
- label="Data"
35
- active={activeTab === "data"}
36
- onClick={() => setActiveTab("data")}
37
- />
45
+ {tabs.map(tab => (
46
+ <SettingsNavItem
47
+ key={tab.key}
48
+ label={tab.label}
49
+ active={activeTab === tab.key}
50
+ onClick={() => setActiveTab(tab.key)}
51
+ />
52
+ ))}
38
53
  </nav>
39
54
  </div>
40
55
 
41
56
  {/* Settings Content */}
42
- <div className="flex-1 overflow-auto p-6">
57
+ <div className="flex-1 overflow-auto p-4 md:p-6">
43
58
  {activeTab === "providers" && <ProvidersSettings />}
44
- {activeTab === "projects" && <ProjectsSettings />}
59
+ {activeTab === "projects" && projectsEnabled && <ProjectsSettings />}
45
60
  {activeTab === "updates" && <UpdatesSettings />}
46
61
  {activeTab === "data" && <DataSettings />}
47
62
  </div>
@@ -121,11 +136,22 @@ function ProvidersSettings() {
121
136
  body: JSON.stringify({ key: apiKey }),
122
137
  });
123
138
 
139
+ const saveData = await saveRes.json();
124
140
  if (!saveRes.ok) {
125
- const data = await saveRes.json();
126
- setError(data.error || "Failed to save key");
141
+ setError(saveData.error || "Failed to save key");
127
142
  } else {
128
- setSuccess("API key saved!");
143
+ // Build success message including agent restart info
144
+ let msg = "API key saved!";
145
+ if (saveData.restartedAgents && saveData.restartedAgents.length > 0) {
146
+ const successCount = saveData.restartedAgents.filter((a: { success: boolean }) => a.success).length;
147
+ const failCount = saveData.restartedAgents.length - successCount;
148
+ if (failCount === 0) {
149
+ msg += ` Restarted ${successCount} agent${successCount > 1 ? 's' : ''} with new key.`;
150
+ } else {
151
+ msg += ` Restarted ${successCount}/${saveData.restartedAgents.length} agents.`;
152
+ }
153
+ }
154
+ setSuccess(msg);
129
155
  setApiKey("");
130
156
  setSelectedProvider(null);
131
157
  fetchProviders();
@@ -148,10 +174,34 @@ function ProvidersSettings() {
148
174
  const llmConfiguredCount = llmProviders.filter(p => p.hasKey).length;
149
175
  const intConfiguredCount = integrations.filter(p => p.hasKey).length;
150
176
 
177
+ // Auto-dismiss success message after 5 seconds
178
+ useEffect(() => {
179
+ if (success && !selectedProvider) {
180
+ const timer = setTimeout(() => setSuccess(null), 5000);
181
+ return () => clearTimeout(timer);
182
+ }
183
+ }, [success, selectedProvider]);
184
+
151
185
  return (
152
186
  <>
153
187
  {ConfirmDialog}
154
188
  <div className="space-y-10">
189
+ {/* Global Success Banner */}
190
+ {success && !selectedProvider && (
191
+ <div className="bg-green-500/10 border border-green-500/30 rounded-lg p-4 flex items-center justify-between">
192
+ <div className="flex items-center gap-2 text-green-400">
193
+ <CheckIcon className="w-5 h-5" />
194
+ <span>{success}</span>
195
+ </div>
196
+ <button
197
+ onClick={() => setSuccess(null)}
198
+ className="text-green-400 hover:text-green-300"
199
+ >
200
+ <CloseIcon className="w-4 h-4" />
201
+ </button>
202
+ </div>
203
+ )}
204
+
155
205
  {/* AI Providers Section */}
156
206
  <div>
157
207
  <div className="mb-6">
@@ -768,73 +818,79 @@ function ProviderKeyCard({
768
818
  )}
769
819
  </div>
770
820
 
771
- {provider.hasKey ? (
772
- <div className="flex items-center justify-between mt-3 pt-3 border-t border-[#1a1a1a]">
773
- <a
774
- href={provider.docsUrl}
775
- target="_blank"
776
- rel="noopener noreferrer"
777
- className="text-sm text-[#3b82f6] hover:underline"
778
- >
779
- View docs
780
- </a>
781
- <button
782
- onClick={onDelete}
783
- className="text-red-400 hover:text-red-300 text-sm"
784
- >
785
- Remove key
786
- </button>
787
- </div>
788
- ) : (
789
- <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
790
- {isEditing ? (
791
- <div className="space-y-3">
792
- <input
793
- type="password"
794
- value={apiKey}
795
- onChange={e => onApiKeyChange(e.target.value)}
796
- placeholder="Enter API key..."
797
- autoFocus
798
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
799
- />
800
- {error && <p className="text-red-400 text-sm">{error}</p>}
801
- {success && <p className="text-green-400 text-sm">{success}</p>}
802
- <div className="flex gap-2">
803
- <button
804
- onClick={onCancelEdit}
805
- className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
806
- >
807
- Cancel
808
- </button>
809
- <button
810
- onClick={onSave}
811
- disabled={!apiKey || saving}
812
- className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
813
- >
814
- {testing ? "Validating..." : saving ? "Saving..." : "Save"}
815
- </button>
816
- </div>
817
- </div>
818
- ) : (
819
- <div className="flex items-center justify-between">
820
- <a
821
- href={provider.docsUrl}
822
- target="_blank"
823
- rel="noopener noreferrer"
824
- className="text-sm text-[#3b82f6] hover:underline"
821
+ <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
822
+ {isEditing ? (
823
+ <div className="space-y-3">
824
+ <input
825
+ type="password"
826
+ value={apiKey}
827
+ onChange={e => onApiKeyChange(e.target.value)}
828
+ placeholder={provider.hasKey ? "Enter new API key..." : "Enter API key..."}
829
+ autoFocus
830
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
831
+ />
832
+ {error && <p className="text-red-400 text-sm">{error}</p>}
833
+ {success && <p className="text-green-400 text-sm">{success}</p>}
834
+ <div className="flex gap-2">
835
+ <button
836
+ onClick={onCancelEdit}
837
+ className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
838
+ >
839
+ Cancel
840
+ </button>
841
+ <button
842
+ onClick={onSave}
843
+ disabled={!apiKey || saving}
844
+ className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
825
845
  >
826
- Get API key
827
- </a>
846
+ {testing ? "Validating..." : saving ? "Saving..." : "Save"}
847
+ </button>
848
+ </div>
849
+ </div>
850
+ ) : provider.hasKey ? (
851
+ <div className="flex items-center justify-between">
852
+ <a
853
+ href={provider.docsUrl}
854
+ target="_blank"
855
+ rel="noopener noreferrer"
856
+ className="text-sm text-[#3b82f6] hover:underline"
857
+ >
858
+ View docs
859
+ </a>
860
+ <div className="flex items-center gap-3">
828
861
  <button
829
862
  onClick={onStartEdit}
830
- className="text-sm text-[#f97316] hover:text-[#fb923c]"
863
+ className="text-sm text-[#888] hover:text-[#e0e0e0]"
864
+ >
865
+ Update key
866
+ </button>
867
+ <button
868
+ onClick={onDelete}
869
+ className="text-red-400 hover:text-red-300 text-sm"
831
870
  >
832
- + Add key
871
+ Remove
833
872
  </button>
834
873
  </div>
835
- )}
836
- </div>
837
- )}
874
+ </div>
875
+ ) : (
876
+ <div className="flex items-center justify-between">
877
+ <a
878
+ href={provider.docsUrl}
879
+ target="_blank"
880
+ rel="noopener noreferrer"
881
+ className="text-sm text-[#3b82f6] hover:underline"
882
+ >
883
+ Get API key
884
+ </a>
885
+ <button
886
+ onClick={onStartEdit}
887
+ className="text-sm text-[#f97316] hover:text-[#fb923c]"
888
+ >
889
+ + Add key
890
+ </button>
891
+ </div>
892
+ )}
893
+ </div>
838
894
  </div>
839
895
  );
840
896
  }
@@ -874,73 +930,79 @@ function IntegrationKeyCard({
874
930
  )}
875
931
  </div>
876
932
 
877
- {provider.hasKey ? (
878
- <div className="flex items-center justify-between mt-3 pt-3 border-t border-[#1a1a1a]">
879
- <a
880
- href={provider.docsUrl}
881
- target="_blank"
882
- rel="noopener noreferrer"
883
- className="text-sm text-[#3b82f6] hover:underline"
884
- >
885
- View docs
886
- </a>
887
- <button
888
- onClick={onDelete}
889
- className="text-red-400 hover:text-red-300 text-sm"
890
- >
891
- Remove key
892
- </button>
893
- </div>
894
- ) : (
895
- <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
896
- {isEditing ? (
897
- <div className="space-y-3">
898
- <input
899
- type="password"
900
- value={apiKey}
901
- onChange={e => onApiKeyChange(e.target.value)}
902
- placeholder="Enter API key..."
903
- autoFocus
904
- className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
905
- />
906
- {error && <p className="text-red-400 text-sm">{error}</p>}
907
- {success && <p className="text-green-400 text-sm">{success}</p>}
908
- <div className="flex gap-2">
909
- <button
910
- onClick={onCancelEdit}
911
- className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
912
- >
913
- Cancel
914
- </button>
915
- <button
916
- onClick={onSave}
917
- disabled={!apiKey || saving}
918
- className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
919
- >
920
- {testing ? "Validating..." : saving ? "Saving..." : "Save"}
921
- </button>
922
- </div>
923
- </div>
924
- ) : (
925
- <div className="flex items-center justify-between">
926
- <a
927
- href={provider.docsUrl}
928
- target="_blank"
929
- rel="noopener noreferrer"
930
- className="text-sm text-[#3b82f6] hover:underline"
933
+ <div className="mt-3 pt-3 border-t border-[#1a1a1a]">
934
+ {isEditing ? (
935
+ <div className="space-y-3">
936
+ <input
937
+ type="password"
938
+ value={apiKey}
939
+ onChange={e => onApiKeyChange(e.target.value)}
940
+ placeholder={provider.hasKey ? "Enter new API key..." : "Enter API key..."}
941
+ autoFocus
942
+ className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
943
+ />
944
+ {error && <p className="text-red-400 text-sm">{error}</p>}
945
+ {success && <p className="text-green-400 text-sm">{success}</p>}
946
+ <div className="flex gap-2">
947
+ <button
948
+ onClick={onCancelEdit}
949
+ className="flex-1 px-3 py-1.5 border border-[#333] rounded text-sm hover:border-[#666]"
950
+ >
951
+ Cancel
952
+ </button>
953
+ <button
954
+ onClick={onSave}
955
+ disabled={!apiKey || saving}
956
+ className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
931
957
  >
932
- Get API key
933
- </a>
958
+ {testing ? "Validating..." : saving ? "Saving..." : "Save"}
959
+ </button>
960
+ </div>
961
+ </div>
962
+ ) : provider.hasKey ? (
963
+ <div className="flex items-center justify-between">
964
+ <a
965
+ href={provider.docsUrl}
966
+ target="_blank"
967
+ rel="noopener noreferrer"
968
+ className="text-sm text-[#3b82f6] hover:underline"
969
+ >
970
+ View docs
971
+ </a>
972
+ <div className="flex items-center gap-3">
934
973
  <button
935
974
  onClick={onStartEdit}
936
- className="text-sm text-[#f97316] hover:text-[#fb923c]"
975
+ className="text-sm text-[#888] hover:text-[#e0e0e0]"
976
+ >
977
+ Update key
978
+ </button>
979
+ <button
980
+ onClick={onDelete}
981
+ className="text-red-400 hover:text-red-300 text-sm"
937
982
  >
938
- + Add key
983
+ Remove
939
984
  </button>
940
985
  </div>
941
- )}
942
- </div>
943
- )}
986
+ </div>
987
+ ) : (
988
+ <div className="flex items-center justify-between">
989
+ <a
990
+ href={provider.docsUrl}
991
+ target="_blank"
992
+ rel="noopener noreferrer"
993
+ className="text-sm text-[#3b82f6] hover:underline"
994
+ >
995
+ Get API key
996
+ </a>
997
+ <button
998
+ onClick={onStartEdit}
999
+ className="text-sm text-[#f97316] hover:text-[#fb923c]"
1000
+ >
1001
+ + Add key
1002
+ </button>
1003
+ </div>
1004
+ )}
1005
+ </div>
944
1006
  </div>
945
1007
  );
946
1008
  }