apteva 0.3.9 → 0.4.1
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.
- package/dist/App.e10kd3t7.js +227 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/crypto.ts +8 -0
- package/src/providers.ts +19 -0
- package/src/routes/api.ts +57 -0
- package/src/web/components/agents/CreateAgentModal.tsx +47 -11
- package/src/web/components/settings/SettingsPage.tsx +189 -12
- package/dist/App.8v10zwbz.js +0 -227
|
@@ -4,7 +4,7 @@ import { Modal, useConfirm } from "../common/Modal";
|
|
|
4
4
|
import { useProjects, useAuth, type Project } from "../../context";
|
|
5
5
|
import type { Provider } from "../../types";
|
|
6
6
|
|
|
7
|
-
type SettingsTab = "providers" | "projects" | "updates" | "data";
|
|
7
|
+
type SettingsTab = "providers" | "projects" | "account" | "updates" | "data";
|
|
8
8
|
|
|
9
9
|
export function SettingsPage() {
|
|
10
10
|
const { projectsEnabled } = useProjects();
|
|
@@ -13,6 +13,7 @@ export function SettingsPage() {
|
|
|
13
13
|
const tabs: { key: SettingsTab; label: string }[] = [
|
|
14
14
|
{ key: "providers", label: "Providers" },
|
|
15
15
|
...(projectsEnabled ? [{ key: "projects" as SettingsTab, label: "Projects" }] : []),
|
|
16
|
+
{ key: "account", label: "Account" },
|
|
16
17
|
{ key: "updates", label: "Updates" },
|
|
17
18
|
{ key: "data", label: "Data" },
|
|
18
19
|
];
|
|
@@ -57,6 +58,7 @@ export function SettingsPage() {
|
|
|
57
58
|
<div className="flex-1 overflow-auto p-4 md:p-6">
|
|
58
59
|
{activeTab === "providers" && <ProvidersSettings />}
|
|
59
60
|
{activeTab === "projects" && projectsEnabled && <ProjectsSettings />}
|
|
61
|
+
{activeTab === "account" && <AccountSettings />}
|
|
60
62
|
{activeTab === "updates" && <UpdatesSettings />}
|
|
61
63
|
{activeTab === "data" && <DataSettings />}
|
|
62
64
|
</div>
|
|
@@ -793,6 +795,19 @@ function ProviderKeyCard({
|
|
|
793
795
|
onSave,
|
|
794
796
|
onDelete,
|
|
795
797
|
}: ProviderKeyCardProps) {
|
|
798
|
+
const isOllama = provider.id === "ollama";
|
|
799
|
+
const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number } | null>(null);
|
|
800
|
+
|
|
801
|
+
// Check Ollama status when configured
|
|
802
|
+
React.useEffect(() => {
|
|
803
|
+
if (isOllama && provider.hasKey) {
|
|
804
|
+
fetch("/api/providers/ollama/status")
|
|
805
|
+
.then(res => res.json())
|
|
806
|
+
.then(data => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount }))
|
|
807
|
+
.catch(() => setOllamaStatus({ connected: false }));
|
|
808
|
+
}
|
|
809
|
+
}, [isOllama, provider.hasKey]);
|
|
810
|
+
|
|
796
811
|
return (
|
|
797
812
|
<div className={`bg-[#111] border rounded-lg p-4 ${
|
|
798
813
|
provider.hasKey ? 'border-green-500/20' : 'border-[#1a1a1a]'
|
|
@@ -803,13 +818,28 @@ function ProviderKeyCard({
|
|
|
803
818
|
<p className="text-sm text-[#666] truncate">
|
|
804
819
|
{provider.type === "integration"
|
|
805
820
|
? (provider.description || "MCP integration")
|
|
806
|
-
:
|
|
821
|
+
: isOllama
|
|
822
|
+
? "Run models locally"
|
|
823
|
+
: `${provider.models.length} models`}
|
|
807
824
|
</p>
|
|
808
825
|
</div>
|
|
809
826
|
{provider.hasKey ? (
|
|
810
|
-
<span className=
|
|
811
|
-
|
|
812
|
-
|
|
827
|
+
<span className={`text-xs flex items-center gap-1 px-2 py-1 rounded whitespace-nowrap flex-shrink-0 ${
|
|
828
|
+
isOllama && ollamaStatus
|
|
829
|
+
? ollamaStatus.connected
|
|
830
|
+
? "text-green-400 bg-green-500/10"
|
|
831
|
+
: "text-yellow-400 bg-yellow-500/10"
|
|
832
|
+
: "text-green-400 bg-green-500/10"
|
|
833
|
+
}`}>
|
|
834
|
+
{isOllama && ollamaStatus ? (
|
|
835
|
+
ollamaStatus.connected ? (
|
|
836
|
+
<><CheckIcon className="w-3 h-3" />{ollamaStatus.modelCount} models</>
|
|
837
|
+
) : (
|
|
838
|
+
<>Not running</>
|
|
839
|
+
)
|
|
840
|
+
) : (
|
|
841
|
+
<><CheckIcon className="w-3 h-3" />{provider.keyHint}</>
|
|
842
|
+
)}
|
|
813
843
|
</span>
|
|
814
844
|
) : (
|
|
815
845
|
<span className="text-[#666] text-xs bg-[#1a1a1a] px-2 py-1 rounded whitespace-nowrap flex-shrink-0">
|
|
@@ -822,13 +852,20 @@ function ProviderKeyCard({
|
|
|
822
852
|
{isEditing ? (
|
|
823
853
|
<div className="space-y-3">
|
|
824
854
|
<input
|
|
825
|
-
type="password"
|
|
855
|
+
type={isOllama ? "text" : "password"}
|
|
826
856
|
value={apiKey}
|
|
827
857
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
828
|
-
placeholder={
|
|
858
|
+
placeholder={isOllama
|
|
859
|
+
? "http://localhost:11434"
|
|
860
|
+
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
829
861
|
autoFocus
|
|
830
862
|
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
831
863
|
/>
|
|
864
|
+
{isOllama && (
|
|
865
|
+
<p className="text-xs text-[#666]">
|
|
866
|
+
Enter your Ollama server URL. Default is http://localhost:11434
|
|
867
|
+
</p>
|
|
868
|
+
)}
|
|
832
869
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
833
870
|
{success && <p className="text-green-400 text-sm">{success}</p>}
|
|
834
871
|
<div className="flex gap-2">
|
|
@@ -843,7 +880,7 @@ function ProviderKeyCard({
|
|
|
843
880
|
disabled={!apiKey || saving}
|
|
844
881
|
className="flex-1 px-3 py-1.5 bg-[#f97316] text-black rounded text-sm font-medium disabled:opacity-50"
|
|
845
882
|
>
|
|
846
|
-
{testing ? "Validating..." : saving ? "Saving..." : "Save"}
|
|
883
|
+
{testing ? "Validating..." : saving ? "Saving..." : isOllama ? "Connect" : "Save"}
|
|
847
884
|
</button>
|
|
848
885
|
</div>
|
|
849
886
|
</div>
|
|
@@ -855,14 +892,14 @@ function ProviderKeyCard({
|
|
|
855
892
|
rel="noopener noreferrer"
|
|
856
893
|
className="text-sm text-[#3b82f6] hover:underline"
|
|
857
894
|
>
|
|
858
|
-
View docs
|
|
895
|
+
{isOllama ? "Download Ollama" : "View docs"}
|
|
859
896
|
</a>
|
|
860
897
|
<div className="flex items-center gap-3">
|
|
861
898
|
<button
|
|
862
899
|
onClick={onStartEdit}
|
|
863
900
|
className="text-sm text-[#888] hover:text-[#e0e0e0]"
|
|
864
901
|
>
|
|
865
|
-
Update key
|
|
902
|
+
{isOllama ? "Change URL" : "Update key"}
|
|
866
903
|
</button>
|
|
867
904
|
<button
|
|
868
905
|
onClick={onDelete}
|
|
@@ -880,13 +917,13 @@ function ProviderKeyCard({
|
|
|
880
917
|
rel="noopener noreferrer"
|
|
881
918
|
className="text-sm text-[#3b82f6] hover:underline"
|
|
882
919
|
>
|
|
883
|
-
Get API key
|
|
920
|
+
{isOllama ? "Download Ollama" : "Get API key"}
|
|
884
921
|
</a>
|
|
885
922
|
<button
|
|
886
923
|
onClick={onStartEdit}
|
|
887
924
|
className="text-sm text-[#f97316] hover:text-[#fb923c]"
|
|
888
925
|
>
|
|
889
|
-
+ Add key
|
|
926
|
+
{isOllama ? "Configure" : "+ Add key"}
|
|
890
927
|
</button>
|
|
891
928
|
</div>
|
|
892
929
|
)}
|
|
@@ -1007,6 +1044,146 @@ function IntegrationKeyCard({
|
|
|
1007
1044
|
);
|
|
1008
1045
|
}
|
|
1009
1046
|
|
|
1047
|
+
function AccountSettings() {
|
|
1048
|
+
const { authFetch, user } = useAuth();
|
|
1049
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
1050
|
+
const [newPassword, setNewPassword] = useState("");
|
|
1051
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
1052
|
+
const [saving, setSaving] = useState(false);
|
|
1053
|
+
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
|
1054
|
+
|
|
1055
|
+
const handleChangePassword = async () => {
|
|
1056
|
+
// Validation
|
|
1057
|
+
if (!currentPassword || !newPassword || !confirmPassword) {
|
|
1058
|
+
setMessage({ type: "error", text: "All fields are required" });
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (newPassword !== confirmPassword) {
|
|
1063
|
+
setMessage({ type: "error", text: "New passwords do not match" });
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (newPassword.length < 8) {
|
|
1068
|
+
setMessage({ type: "error", text: "Password must be at least 8 characters" });
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
setSaving(true);
|
|
1073
|
+
setMessage(null);
|
|
1074
|
+
|
|
1075
|
+
try {
|
|
1076
|
+
const res = await authFetch("/api/auth/password", {
|
|
1077
|
+
method: "PUT",
|
|
1078
|
+
headers: { "Content-Type": "application/json" },
|
|
1079
|
+
body: JSON.stringify({ currentPassword, newPassword }),
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
const data = await res.json();
|
|
1083
|
+
|
|
1084
|
+
if (res.ok) {
|
|
1085
|
+
setMessage({ type: "success", text: "Password updated successfully" });
|
|
1086
|
+
setCurrentPassword("");
|
|
1087
|
+
setNewPassword("");
|
|
1088
|
+
setConfirmPassword("");
|
|
1089
|
+
} else {
|
|
1090
|
+
setMessage({ type: "error", text: data.error || "Failed to update password" });
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
setMessage({ type: "error", text: "Failed to update password" });
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
setSaving(false);
|
|
1097
|
+
};
|
|
1098
|
+
|
|
1099
|
+
return (
|
|
1100
|
+
<div className="max-w-4xl w-full">
|
|
1101
|
+
<div className="mb-6">
|
|
1102
|
+
<h1 className="text-2xl font-semibold mb-1">Account Settings</h1>
|
|
1103
|
+
<p className="text-[#666]">Manage your account and security.</p>
|
|
1104
|
+
</div>
|
|
1105
|
+
|
|
1106
|
+
{/* User Info */}
|
|
1107
|
+
{user && (
|
|
1108
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 mb-6">
|
|
1109
|
+
<h3 className="font-medium mb-3">Profile</h3>
|
|
1110
|
+
<div className="space-y-2 text-sm">
|
|
1111
|
+
<div className="flex justify-between">
|
|
1112
|
+
<span className="text-[#666]">Username</span>
|
|
1113
|
+
<span>{user.username}</span>
|
|
1114
|
+
</div>
|
|
1115
|
+
{user.email && (
|
|
1116
|
+
<div className="flex justify-between">
|
|
1117
|
+
<span className="text-[#666]">Email</span>
|
|
1118
|
+
<span>{user.email}</span>
|
|
1119
|
+
</div>
|
|
1120
|
+
)}
|
|
1121
|
+
<div className="flex justify-between">
|
|
1122
|
+
<span className="text-[#666]">Role</span>
|
|
1123
|
+
<span className="capitalize">{user.role}</span>
|
|
1124
|
+
</div>
|
|
1125
|
+
</div>
|
|
1126
|
+
</div>
|
|
1127
|
+
)}
|
|
1128
|
+
|
|
1129
|
+
{/* Change Password */}
|
|
1130
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
|
|
1131
|
+
<h3 className="font-medium mb-4">Change Password</h3>
|
|
1132
|
+
|
|
1133
|
+
<div className="space-y-4 max-w-md">
|
|
1134
|
+
<div>
|
|
1135
|
+
<label className="block text-sm text-[#666] mb-1">Current Password</label>
|
|
1136
|
+
<input
|
|
1137
|
+
type="password"
|
|
1138
|
+
value={currentPassword}
|
|
1139
|
+
onChange={(e) => setCurrentPassword(e.target.value)}
|
|
1140
|
+
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
1141
|
+
/>
|
|
1142
|
+
</div>
|
|
1143
|
+
|
|
1144
|
+
<div>
|
|
1145
|
+
<label className="block text-sm text-[#666] mb-1">New Password</label>
|
|
1146
|
+
<input
|
|
1147
|
+
type="password"
|
|
1148
|
+
value={newPassword}
|
|
1149
|
+
onChange={(e) => setNewPassword(e.target.value)}
|
|
1150
|
+
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
1151
|
+
/>
|
|
1152
|
+
</div>
|
|
1153
|
+
|
|
1154
|
+
<div>
|
|
1155
|
+
<label className="block text-sm text-[#666] mb-1">Confirm New Password</label>
|
|
1156
|
+
<input
|
|
1157
|
+
type="password"
|
|
1158
|
+
value={confirmPassword}
|
|
1159
|
+
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
1160
|
+
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
1161
|
+
/>
|
|
1162
|
+
</div>
|
|
1163
|
+
|
|
1164
|
+
{message && (
|
|
1165
|
+
<div className={`p-3 rounded text-sm ${
|
|
1166
|
+
message.type === "success"
|
|
1167
|
+
? "bg-green-500/10 text-green-400 border border-green-500/30"
|
|
1168
|
+
: "bg-red-500/10 text-red-400 border border-red-500/30"
|
|
1169
|
+
}`}>
|
|
1170
|
+
{message.text}
|
|
1171
|
+
</div>
|
|
1172
|
+
)}
|
|
1173
|
+
|
|
1174
|
+
<button
|
|
1175
|
+
onClick={handleChangePassword}
|
|
1176
|
+
disabled={saving || !currentPassword || !newPassword || !confirmPassword}
|
|
1177
|
+
className="px-4 py-2 bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 disabled:cursor-not-allowed text-black rounded text-sm font-medium transition"
|
|
1178
|
+
>
|
|
1179
|
+
{saving ? "Updating..." : "Update Password"}
|
|
1180
|
+
</button>
|
|
1181
|
+
</div>
|
|
1182
|
+
</div>
|
|
1183
|
+
</div>
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1010
1187
|
function DataSettings() {
|
|
1011
1188
|
const { authFetch } = useAuth();
|
|
1012
1189
|
const [clearing, setClearing] = useState(false);
|