Skip to content

Commit a6a32c3

Browse files
authored
Fix/issues (#167)
1 parent 87691a5 commit a6a32c3

28 files changed

+1585
-1156
lines changed

web-ui/Providers.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22

33
import { RainbowProvider } from "./src/shared/providers/RainbowProvider";
44
import { BrokerProvider } from "./src/shared/providers/BrokerProvider";
5+
import { DepositGuardProvider } from "./src/shared/providers/DepositGuardProvider";
56

67
export function Providers({ children }: { children: React.ReactNode }) {
78
return (
89
<RainbowProvider>
9-
<BrokerProvider>{children}</BrokerProvider>
10+
<BrokerProvider>
11+
<DepositGuardProvider>{children}</DepositGuardProvider>
12+
</BrokerProvider>
1013
</RainbowProvider>
1114
);
1215
}

web-ui/src/app/inference/chat/components/OptimizedChatPage.tsx

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import * as React from "react";
44
import { useState, useEffect, useRef, useCallback } from "react";
55
import { useAccount } from "wagmi";
6+
import { useConnectModal } from "@rainbow-me/rainbowkit";
67
import { useBroker } from "@/shared/providers/BrokerProvider";
8+
import { useDepositGuard } from "@/shared/providers/DepositGuardProvider";
79
import { useChatHistory } from "../../../../shared/hooks/useChatHistory";
810
import { useProviderSearch } from "../../hooks/useProviderSearch";
911
import { useStreamingState } from "../../../../shared/hooks/useStreamingState";
@@ -27,23 +29,14 @@ import {
2729
AlertDialogHeader,
2830
AlertDialogTitle,
2931
} from "@/components/ui/alert-dialog";
30-
import type { Provider } from "../../../../shared/types/broker";
31-
32-
33-
34-
interface Message {
35-
role: "system" | "user" | "assistant";
36-
content: string;
37-
timestamp?: number;
38-
chatId?: string;
39-
isVerified?: boolean | null;
40-
isVerifying?: boolean;
41-
}
32+
import type { Provider, Message } from "../../../../shared/types/broker";
4233

4334

4435
export function OptimizedChatPage() {
4536
const { isConnected, address } = useAccount();
46-
const { broker, isInitializing, ledgerInfo, refreshLedgerInfo } = useBroker();
37+
const { broker, readOnlyBroker, isInitializing, ledgerInfo, refreshLedgerInfo } = useBroker();
38+
const { openConnectModal } = useConnectModal();
39+
const { requestDeposit } = useDepositGuard();
4740
const { toast } = useToast();
4841

4942
// Use toast for non-blocking errors
@@ -67,7 +60,7 @@ export function OptimizedChatPage() {
6760
providerPendingRefund,
6861
setSelectedProvider,
6962
refreshProviderBalance,
70-
} = useProviderManagement(broker);
63+
} = useProviderManagement(broker, readOnlyBroker);
7164

7265
// Provider dropdown state (UI only)
7366
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@@ -171,6 +164,8 @@ export function OptimizedChatPage() {
171164
setErrorWithTimeout,
172165
isUserScrollingRef,
173166
messagesEndRef,
167+
openConnectModal,
168+
requestDeposit,
174169
});
175170

176171
// Handle editing a user message - truncates conversation and resends
@@ -445,38 +440,6 @@ export function OptimizedChatPage() {
445440
// Note: handleDeposit is now handled globally in LayoutContent
446441

447442

448-
if (!isConnected) {
449-
return (
450-
<div className="w-full">
451-
<div className="bg-white rounded-xl border border-gray-200 p-8 text-center">
452-
<div className="flex items-center justify-center mb-6">
453-
<div className="w-16 h-16 bg-purple-50 rounded-full flex items-center justify-center border border-purple-200">
454-
<svg
455-
className="w-8 h-8 text-purple-600"
456-
fill="none"
457-
stroke="currentColor"
458-
viewBox="0 0 24 24"
459-
>
460-
<path
461-
strokeLinecap="round"
462-
strokeLinejoin="round"
463-
strokeWidth={2}
464-
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
465-
/>
466-
</svg>
467-
</div>
468-
</div>
469-
<h3 className="text-lg font-semibold text-gray-900 mb-2">
470-
Wallet Not Connected
471-
</h3>
472-
<p className="text-gray-600">
473-
Please connect your wallet to access AI inference features.
474-
</p>
475-
</div>
476-
</div>
477-
);
478-
}
479-
480443
return (
481444
<div className="w-full">
482445
<div className="mb-1 sm:mb-3">
@@ -564,7 +527,10 @@ export function OptimizedChatPage() {
564527
providerBalanceNeuron={providerBalanceNeuron}
565528
providerPendingRefund={providerPendingRefund}
566529
onAddFunds={() => {
567-
// Use the existing top-up modal logic
530+
if (!broker) {
531+
openConnectModal?.();
532+
return;
533+
}
568534
setShowTopUpModal(true);
569535
}}
570536
/>

web-ui/src/app/inference/chat/components/ProviderSelector.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import React, { useState, useEffect, useMemo } from 'react'
4+
import { useAccount } from 'wagmi'
45
import type { Provider } from '../../../../shared/types/broker'
56
import { OFFICIAL_PROVIDERS } from '../../constants/providers'
67
import { copyToClipboard } from '@/lib/utils'
@@ -17,6 +18,7 @@ import {
1718
getModelHealthStatus,
1819
getHealthStatusColor,
1920
getHealthStatusText,
21+
type ProviderHealthStatus,
2022
} from '@/shared/hooks/useProviderHealth'
2123
import { formatNumber } from '@/shared/utils/formatNumber'
2224

@@ -78,7 +80,7 @@ function MobileProviderCard({
7880
isSelected: boolean
7981
isRecentlyUsed: boolean
8082
onSelect: () => void
81-
healthData: Map<string, any[]>
83+
healthData: Map<string, ProviderHealthStatus[]>
8284
isLoadingHealth: boolean
8385
}) {
8486
const isTeeVerified =
@@ -217,16 +219,14 @@ export function ProviderSelector({
217219
onAddFunds,
218220
}: ProviderSelectorProps) {
219221
const isMobile = useIsMobile()
222+
const { isConnected } = useAccount()
220223
const [mobileSearchQuery, setMobileSearchQuery] = useState('')
221224

222225
// Get health data using SWR hook (automatically cached across components)
223226
const { healthData, isLoading: isLoadingHealth } = useProviderHealth()
224227

225228
// Recently used providers set for quick lookup
226-
const recentlyUsedSet = useMemo(() => {
227-
const used = getRecentlyUsedProviders()
228-
return new Set(used)
229-
}, [])
229+
const recentlyUsedSet = new Set(getRecentlyUsedProviders())
230230

231231
// Filter out unverified providers - only show verified providers that can be used
232232
const verifiedProviders = useMemo(() => {
@@ -715,7 +715,13 @@ export function ProviderSelector({
715715
)}
716716
{/* Right Section: Balance and Add Funds */}
717717
<div className="flex items-center gap-1 sm:gap-1.5 px-1 sm:px-2 py-1 rounded-md">
718-
<div
718+
{!isConnected ? (
719+
<span className="text-xs text-red-500 whitespace-nowrap px-1.5 py-1">
720+
Wallet not connected
721+
</span>
722+
) : (
723+
<>
724+
<div
719725
className={`flex items-center gap-1 sm:gap-1.5 px-1.5 sm:px-2 py-1 rounded-md text-xs ${
720726
(providerBalanceNeuron !== null &&
721727
providerBalanceNeuron === BigInt(0)) ||
@@ -864,6 +870,8 @@ export function ProviderSelector({
864870
</svg>
865871
<span className="hidden sm:inline">Add Funds</span>
866872
</button>
873+
</>
874+
)}
867875
</div>
868876
</div>
869877
)}

web-ui/src/app/inference/chat/components/TopUpModal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import * as React from "react";
44
import { useState } from "react";
5+
import type { ZGComputeNetworkBroker } from '@0glabs/0g-serving-broker';
56
import { a0giToNeuron } from "../../../../shared/utils/currency";
67
import { formatNumber } from "../../../../shared/utils/formatNumber";
78
import {
@@ -44,7 +45,7 @@ interface LedgerInfo {
4445
interface TopUpModalProps {
4546
isOpen: boolean;
4647
onClose: () => void;
47-
broker: any; // TODO: Replace with proper broker type when available
48+
broker: ZGComputeNetworkBroker | null;
4849
selectedProvider: Provider | null;
4950
topUpAmount: string;
5051
setTopUpAmount: (amount: string) => void;
@@ -53,7 +54,7 @@ interface TopUpModalProps {
5354
providerBalance: number | null;
5455
providerPendingRefund: number | null;
5556
ledgerInfo: LedgerInfo | null;
56-
refreshLedgerInfo: () => Promise<void>;
57+
refreshLedgerInfo: () => Promise<LedgerInfo | null>;
5758
refreshProviderBalance: () => Promise<void>;
5859
setErrorWithTimeout: (error: string | null) => void;
5960
}
@@ -275,6 +276,8 @@ export function TopUpModal({
275276
<Button
276277
onClick={handleTopUp}
277278
disabled={
279+
!broker ||
280+
!selectedProvider ||
278281
isTopping ||
279282
!topUpAmount ||
280283
parseFloat(topUpAmount) < MINIMUM_DEPOSITS.TOPUP_PROVIDER ||
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use client'
2+
3+
import * as React from 'react'
4+
import { Card, CardContent } from '@/components/ui/card'
5+
import { Badge } from '@/components/ui/badge'
6+
import {
7+
MessageCircle,
8+
Image,
9+
Mic,
10+
Wand2,
11+
Users,
12+
Shield,
13+
ChevronRight,
14+
} from 'lucide-react'
15+
import { cn } from '@/lib/utils'
16+
import type { ModelSummary } from '@/shared/types/broker'
17+
18+
interface ModelCardProps {
19+
model: ModelSummary
20+
onClick: (model: ModelSummary) => void
21+
}
22+
23+
const SERVICE_TYPE_CONFIG: Record<string, { label: string; icon: React.ElementType; color: string; hoverColor: string }> = {
24+
chatbot: { label: 'Chatbot', icon: MessageCircle, color: 'bg-blue-100 text-blue-700', hoverColor: 'hover:bg-blue-100 hover:text-blue-700' },
25+
'text-to-image': { label: 'Text to Image', icon: Image, color: 'bg-purple-100 text-purple-700', hoverColor: 'hover:bg-purple-100 hover:text-purple-700' },
26+
'image-editing': { label: 'Image Editing', icon: Wand2, color: 'bg-pink-100 text-pink-700', hoverColor: 'hover:bg-pink-100 hover:text-pink-700' },
27+
'speech-to-text': { label: 'Speech to Text', icon: Mic, color: 'bg-amber-100 text-amber-700', hoverColor: 'hover:bg-amber-100 hover:text-amber-700' },
28+
}
29+
30+
function formatPriceRange(range: { min: number; max: number } | null): string | null {
31+
if (!range) return null
32+
if (range.min === range.max) {
33+
return range.min.toFixed(2)
34+
}
35+
return `${range.min.toFixed(2)} - ${range.max.toFixed(2)}`
36+
}
37+
38+
export function ModelCard({ model, onClick }: ModelCardProps) {
39+
const typeConfig = SERVICE_TYPE_CONFIG[model.serviceType] || SERVICE_TYPE_CONFIG.chatbot
40+
const TypeIcon = typeConfig.icon
41+
const isImageService = model.serviceType === 'text-to-image' || model.serviceType === 'image-editing'
42+
43+
const priceDisplay = isImageService
44+
? formatPriceRange(model.outputPriceRange)
45+
: formatPriceRange(model.inputPriceRange)
46+
47+
const priceUnit = isImageService ? '0G/image' : '0G/1M tokens'
48+
49+
return (
50+
<Card
51+
className="relative group cursor-pointer hover:shadow-glow"
52+
onClick={() => onClick(model)}
53+
>
54+
<CardContent className="p-5">
55+
{/* Header */}
56+
<div className="flex items-start justify-between mb-3">
57+
<div className="flex-1 min-w-0">
58+
<h3 className="text-base font-semibold text-foreground truncate mb-2">
59+
{model.displayName}
60+
</h3>
61+
<Badge
62+
className={cn(
63+
'border-0 px-2 py-0.5 text-xs font-medium flex items-center gap-1 w-fit',
64+
typeConfig.color,
65+
typeConfig.hoverColor,
66+
)}
67+
>
68+
<TypeIcon className="h-3 w-3" />
69+
{typeConfig.label}
70+
</Badge>
71+
</div>
72+
<ChevronRight className="h-5 w-5 text-muted-foreground group-hover:text-primary transition-colors flex-shrink-0 mt-1" />
73+
</div>
74+
75+
{/* Stats */}
76+
<div className="flex items-center gap-3 mt-4 text-sm">
77+
<div className="flex items-center gap-1.5 text-muted-foreground">
78+
<Users className="h-3.5 w-3.5" />
79+
<span>
80+
{model.providerCount} provider{model.providerCount !== 1 ? 's' : ''}
81+
</span>
82+
</div>
83+
{model.verifiedCount > 0 && (
84+
<div className="flex items-center gap-1.5 text-green-600">
85+
<Shield className="h-3.5 w-3.5" />
86+
<span>{model.verifiedCount} verified</span>
87+
</div>
88+
)}
89+
</div>
90+
91+
{/* Price range */}
92+
{priceDisplay && (
93+
<div className="mt-3 flex items-center gap-2 text-xs bg-secondary px-2.5 py-1.5 rounded-lg font-mono">
94+
<span className="text-muted-foreground">Price:</span>
95+
<span className="font-semibold text-foreground">
96+
{priceDisplay}
97+
</span>
98+
<span className="text-muted-foreground">{priceUnit}</span>
99+
</div>
100+
)}
101+
</CardContent>
102+
</Card>
103+
)
104+
}

0 commit comments

Comments
 (0)