feat: complete rebranding to Gemini Nexus with improved UI, proxies and documentation
This commit is contained in:
1 parent
e524a340cb
commit
433dccbaa9
108 files changed
+1004
-1139
No files matched your search
@@ -0,0 +1,113 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCloudAccounts, useSetAccountProxy } from '@/hooks/useCloudAccounts';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { isValidProxyUrl } from '@/utils/url';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { CloudAccount } from '@/types/cloudAccount';
|
||||
|
||||
function AccountProxyItem({ account }: { account: CloudAccount }) {
|
||||
const { t } = useTranslation();
|
||||
const setAccountProxy = useSetAccountProxy();
|
||||
const [proxyUrl, setProxyUrl] = useState(account.proxy_url || '');
|
||||
const [proxySaved, setProxySaved] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4 py-3 border-b last:border-0">
|
||||
<div className="flex items-center gap-3 overflow-hidden">
|
||||
{account.avatar_url ? (
|
||||
<img
|
||||
src={account.avatar_url}
|
||||
alt={account.name || ''}
|
||||
className="bg-muted h-8 w-8 rounded-full border shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="bg-primary/10 text-primary flex h-8 w-8 items-center justify-center rounded-full border shrink-0">
|
||||
{account.name?.[0]?.toUpperCase() || 'A'}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col overflow-hidden">
|
||||
<span className="truncate text-sm font-semibold">
|
||||
{account.name || t('cloud.card.unknown')}
|
||||
</span>
|
||||
<span className="truncate text-xs text-muted-foreground">{account.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={proxyUrl}
|
||||
onChange={(e) => {
|
||||
setProxyUrl(e.target.value);
|
||||
setProxySaved(false);
|
||||
}}
|
||||
onBlur={() => {
|
||||
const trimmed = proxyUrl.trim();
|
||||
if (trimmed && !isValidProxyUrl(trimmed)) {
|
||||
setProxyUrl(account.proxy_url || '');
|
||||
return;
|
||||
}
|
||||
if (trimmed !== (account.proxy_url || '')) {
|
||||
setAccountProxy.mutate({
|
||||
accountId: account.id,
|
||||
proxyUrl: trimmed || null,
|
||||
});
|
||||
setProxySaved(true);
|
||||
setTimeout(() => setProxySaved(false), 2000);
|
||||
}
|
||||
}}
|
||||
placeholder={t('cloud.card.proxyPlaceholder') || 'e.g. http://127.0.0.1:7890'}
|
||||
className="h-8 w-48 text-sm"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div className="w-16">
|
||||
{proxySaved && (
|
||||
<span className="text-[10px] text-green-500 whitespace-nowrap">{t('cloud.card.proxySaved')}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountProxySettings() {
|
||||
const { t } = useTranslation();
|
||||
const { data: accounts, isLoading } = useCloudAccounts();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('settings.proxy.accountProxies.title')}</CardTitle>
|
||||
<CardDescription>{t('settings.proxy.accountProxies.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex justify-center p-6">
|
||||
<Loader2 className="animate-spin text-muted-foreground" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!accounts || accounts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('settings.proxy.accountProxies.title')}</CardTitle>
|
||||
<CardDescription>{t('settings.proxy.accountProxies.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col">
|
||||
{accounts.map((account) => (
|
||||
<AccountProxyItem key={account.id} account={account} />
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -35,9 +35,6 @@ import {
|
||||
getQuotaStatus,
|
||||
type QuotaStatus,
|
||||
} from '@/utils/quota-display';
|
||||
import { useState } from 'react';
|
||||
import { useSetAccountProxy } from '@/hooks/useCloudAccounts';
|
||||
import { isValidProxyUrl } from '@/utils/url';
|
||||
import { getValidationBlockedStatusLabel } from '@/components/accountValidationStatus';
|
||||
|
||||
type ModelQuotaEntry = [string, CloudQuotaModelInfo];
|
||||
@@ -186,9 +183,6 @@ export function CloudAccountCard({
|
||||
isProviderCollapsed,
|
||||
toggleProviderCollapse,
|
||||
} = useProviderGrouping();
|
||||
const setAccountProxy = useSetAccountProxy();
|
||||
const [proxyUrl, setProxyUrl] = useState(account.proxy_url || '');
|
||||
const [proxySaved, setProxySaved] = useState(false);
|
||||
|
||||
const getQuotaTextColorClass = (percentage: number) => {
|
||||
const quotaStatus = getQuotaStatus(percentage);
|
||||
@@ -558,45 +552,11 @@ export function CloudAccountCard({
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="bg-muted/20 text-muted-foreground justify-between border-t p-2 px-4 text-xs">
|
||||
<CardFooter className="bg-muted/20 text-muted-foreground justify-center border-t p-2 px-4 text-xs">
|
||||
<span>
|
||||
{t('cloud.card.used')}{' '}
|
||||
{formatDistanceToNow(account.last_used * 1000, { addSuffix: true })}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={proxyUrl}
|
||||
onChange={(e) => {
|
||||
setProxyUrl(e.target.value);
|
||||
setProxySaved(false);
|
||||
}}
|
||||
onBlur={() => {
|
||||
const trimmed = proxyUrl.trim();
|
||||
if (trimmed && !isValidProxyUrl(trimmed)) {
|
||||
setProxyUrl(account.proxy_url || '');
|
||||
return;
|
||||
}
|
||||
if (trimmed !== (account.proxy_url || '')) {
|
||||
setAccountProxy.mutate({
|
||||
accountId: account.id,
|
||||
proxyUrl: trimmed || null,
|
||||
});
|
||||
setProxySaved(true);
|
||||
setTimeout(() => setProxySaved(false), 2000);
|
||||
}
|
||||
}}
|
||||
placeholder={t('cloud.card.proxyPlaceholder')}
|
||||
className="h-6 w-40 text-xs"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{proxySaved && (
|
||||
<span className="text-[10px] text-green-500">{t('cloud.card.proxySaved')}</span>
|
||||
)}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Loader2, Search, RotateCcw, Save } from 'lucide-react';
|
||||
import { Loader2, Search, RotateCcw, Save, Copy, CheckCircle } from 'lucide-react';
|
||||
import { filter, flatMap, includes, size, sortBy, sumBy, uniq, values } from 'lodash-es';
|
||||
import type { CloudAccount } from '@/types/cloudAccount';
|
||||
|
||||
@@ -44,6 +44,7 @@ export function ModelVisibilitySettings() {
|
||||
const [modelVisibilityMap, setModelVisibilityMap] = useState<Record<string, boolean>>({});
|
||||
const [providerGroupingEnabled, setProviderGroupingEnabled] = useState(false);
|
||||
const [isSavingSettings, setIsSavingSettings] = useState(false);
|
||||
const [copiedModel, setCopiedModel] = useState<string | null>(null);
|
||||
|
||||
// Initialize model visibility and provider groupings from config
|
||||
useEffect(() => {
|
||||
@@ -114,6 +115,14 @@ export function ModelVisibilitySettings() {
|
||||
}));
|
||||
};
|
||||
|
||||
const copyModelId = (modelId: string, e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigator.clipboard.writeText(modelId);
|
||||
setCopiedModel(modelId);
|
||||
setTimeout(() => setCopiedModel(null), 2000);
|
||||
};
|
||||
|
||||
if (accountsLoading) {
|
||||
return (
|
||||
<Card>
|
||||
@@ -188,12 +197,25 @@ export function ModelVisibilitySettings() {
|
||||
handleModelVisibilityChange(modelId, checked as boolean)
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`model-${modelId}`}
|
||||
className="flex-1 cursor-pointer text-sm font-medium"
|
||||
>
|
||||
{modelId}
|
||||
</label>
|
||||
<div className="flex flex-1 items-center gap-2">
|
||||
<label
|
||||
htmlFor={`model-${modelId}`}
|
||||
className="cursor-pointer text-sm font-medium"
|
||||
>
|
||||
{modelId}
|
||||
</label>
|
||||
<button
|
||||
onClick={(e) => copyModelId(modelId, e)}
|
||||
className="text-muted-foreground hover:text-foreground transition-colors"
|
||||
title={t('proxy.copy')}
|
||||
>
|
||||
{copiedModel === modelId ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{!isVisible && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{t('settings.modelVisibility.hidden')}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { isProcessRunning, startAntigravity, closeAntigravity } from '@/actions/process';
|
||||
import { isProcessRunning, startGeminiNexus, closeGeminiNexus } from '@/actions/process';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Play, Square, Loader2, Power } from 'lucide-react';
|
||||
@@ -22,14 +22,14 @@ export const StatusBar: React.FC<StatusBarProps> = ({ isCollapsed = false }) =>
|
||||
});
|
||||
|
||||
const startMutation = useMutation({
|
||||
mutationFn: startAntigravity,
|
||||
mutationFn: startGeminiNexus,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['process', 'status'] });
|
||||
},
|
||||
});
|
||||
|
||||
const stopMutation = useMutation({
|
||||
mutationFn: closeAntigravity,
|
||||
mutationFn: closeGeminiNexus,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['process', 'status'] });
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user