Skip to content

Commit bbf4b65

Browse files
Feat/blockscout ux (#19)
* refactor: centralize chain configuration and integrate Blockscout SDK - Created src/config/viem.ts to centralize chain definitions. - Refactored src/config/wallet.ts to import chains from viem.ts. - Added Blockscout SDK configuration in src/config/blockscout.ts. - Updated utils/web3/chains.ts to import from the new configuration. - Introduced Blockscout helpers for explorer URLs and API integration. * choose models in playground * gpt: models
1 parent 94fef4d commit bbf4b65

File tree

6 files changed

+508
-342
lines changed

6 files changed

+508
-342
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"unreal-console": minor
3+
---
4+
5+
refactor: chain configuration and integrate Blockscout
6+
7+
- Create src/config/viem.ts to centralize chain definitions
8+
- Refactor src/config/wallet.ts to import chains from viem.ts
9+
- Create src/config/blockscout.ts with Blockscout SDK configuration
10+
- Update utils/web3/chains.ts to import from @/config/viem
11+
- Add Blockscout helpers for explorer URLs and API integration

src/config/blockscout.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { CHAINS, CHAIN_DEFAULT } from "@/config/viem"
2+
3+
/**
4+
* Blockscout SDK configuration for blockchain explorer integration.
5+
*
6+
* Provides configuration for Blockscout API integration, including:
7+
* - Primary and supported chain configurations
8+
* - Token and contract mappings
9+
* - Explorer URL helpers
10+
*/
11+
12+
// Blockscout SDK configuration
13+
export const blockscoutConfig = {
14+
primaryChain: {
15+
id: CHAIN_DEFAULT.id,
16+
name: CHAIN_DEFAULT.name,
17+
rpcUrl: CHAIN_DEFAULT.rpcUrls.default.http[0],
18+
explorerUrl: CHAIN_DEFAULT.blockExplorers.default.url,
19+
apiUrl: `${CHAIN_DEFAULT.blockExplorers.default.url}/api/v2`,
20+
nativeCurrency: {
21+
name: CHAIN_DEFAULT.nativeCurrency.name,
22+
symbol: CHAIN_DEFAULT.nativeCurrency.symbol,
23+
decimals: CHAIN_DEFAULT.nativeCurrency.decimals,
24+
},
25+
isTestnet: CHAIN_DEFAULT.testnet,
26+
},
27+
28+
// Multi chain support
29+
supportedChains: CHAINS.map((chain) => ({
30+
id: chain.id,
31+
name: chain.name,
32+
rpcUrl: chain.rpcUrls.default.http[0],
33+
explorerUrl: chain.blockExplorers.default.url,
34+
apiUrl: `${chain.blockExplorers.default.url}/api/v2`,
35+
nativeCurrency: {
36+
name: chain.nativeCurrency.name,
37+
symbol: chain.nativeCurrency.symbol,
38+
decimals: chain.nativeCurrency.decimals,
39+
},
40+
isTestnet: chain.testnet,
41+
isActive: true,
42+
})),
43+
44+
tokens: {
45+
...Object.fromEntries(
46+
CHAINS.map((chain) => {
47+
const custom = chain.custom as
48+
| { tokens?: { UnrealToken?: { address: string; symbol: string; name: string; decimals: number } } }
49+
| undefined
50+
const token = custom?.tokens?.UnrealToken
51+
if (!token) return []
52+
return [
53+
`${token.symbol}_${chain.id}`,
54+
{
55+
address: token.address,
56+
symbol: token.symbol,
57+
name: token.name,
58+
decimals: token.decimals,
59+
chainId: chain.id,
60+
},
61+
]
62+
}).filter((entry) => entry.length > 0)
63+
),
64+
...Object.fromEntries(
65+
CHAINS.map((chain) => [
66+
chain.nativeCurrency.symbol,
67+
{
68+
address: "0x0000000000000000000000000000000000000000",
69+
symbol: chain.nativeCurrency.symbol,
70+
name: chain.nativeCurrency.name,
71+
decimals: chain.nativeCurrency.decimals,
72+
chainId: chain.id,
73+
},
74+
])
75+
),
76+
},
77+
78+
// Contract addresses
79+
contracts: {
80+
...Object.fromEntries(
81+
CHAINS.map((chain) => {
82+
const contracts = chain.contracts as
83+
| { UnrealToken?: { address: string } }
84+
| undefined
85+
const contract = contracts?.UnrealToken
86+
if (!contract) return []
87+
return [
88+
`UnrealToken_${chain.id}`,
89+
{
90+
address: contract.address,
91+
chainId: chain.id,
92+
},
93+
]
94+
}).filter((entry) => entry.length > 0)
95+
),
96+
},
97+
98+
// Notification settings
99+
notifications: {
100+
enabled: true,
101+
position: "top-right" as const,
102+
duration: 5000,
103+
showExplorerLinks: true,
104+
showTransactionDetails: true,
105+
},
106+
107+
// Custom toast types for Unreal operations
108+
customToastTypes: {
109+
TOKEN_APPROVAL: "token_approval",
110+
AIRDROP: "airdrop",
111+
TRANSACTION: "transaction",
112+
},
113+
}
114+
115+
// Helper functions
116+
export const getChainConfig = (chainId: number) => {
117+
return blockscoutConfig.supportedChains.find((chain) => chain.id === chainId)
118+
}
119+
120+
export const getCurrentChainConfig = () => {
121+
try {
122+
const saved =
123+
typeof window !== "undefined"
124+
? window.localStorage.getItem("evm-last-chain-id")
125+
: null
126+
if (saved) {
127+
const id = parseInt(saved, 10)
128+
const found = getChainConfig(id)
129+
if (found) return found
130+
}
131+
} catch {}
132+
return blockscoutConfig.primaryChain
133+
}
134+
135+
export const getTokenConfig = (symbol: string) => {
136+
return blockscoutConfig.tokens[
137+
symbol as keyof typeof blockscoutConfig.tokens
138+
]
139+
}
140+
141+
export const getContractConfig = (contractName: string) => {
142+
return blockscoutConfig.contracts[
143+
contractName as keyof typeof blockscoutConfig.contracts
144+
]
145+
}
146+
147+
export const isChainSupported = (chainId: number) => {
148+
return blockscoutConfig.supportedChains.some((chain) => chain.id === chainId)
149+
}
150+
151+
export const getExplorerUrl = (chainId?: number) => {
152+
const chain = chainId ? getChainConfig(chainId) : getCurrentChainConfig()
153+
return chain?.explorerUrl || blockscoutConfig.primaryChain.explorerUrl
154+
}
155+
156+
export const getApiUrl = (chainId?: number) => {
157+
const chain = chainId ? getChainConfig(chainId) : getCurrentChainConfig()
158+
if (chain?.apiUrl) return chain.apiUrl
159+
if (chain?.explorerUrl) return `${chain.explorerUrl}/api/v2`
160+
return blockscoutConfig.primaryChain.apiUrl
161+
}
162+

src/config/models.ts

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,49 @@ const extractModelId = (m: ApiModelLike): string | undefined => {
2727
return undefined
2828
}
2929

30+
const isExcludedById = (id: string): boolean => {
31+
const lower = id.toLowerCase()
32+
if (lower.includes("embedding")) return true
33+
if (lower.includes("reel")) return true
34+
if (lower.includes("image") || lower.includes("img")) return true
35+
if (
36+
lower.includes("audio") ||
37+
lower.includes("speech") ||
38+
lower.includes("tts") ||
39+
lower.includes("whisper")
40+
)
41+
return true
42+
if (lower.includes("playground")) return true
43+
return false
44+
}
45+
46+
const hasTextCapability = (m: ApiModelLike): boolean => {
47+
if (m && typeof m === "object") {
48+
const obj = m as Record<string, unknown>
49+
const modalities = obj.modalities as unknown
50+
if (
51+
Array.isArray(modalities) &&
52+
modalities.some((x) => String(x).toLowerCase() === "text")
53+
) {
54+
return true
55+
}
56+
const caps = obj.capabilities as Record<string, unknown> | undefined
57+
if (caps && typeof caps === "object") {
58+
const chat = caps["chat"] === true
59+
const responses = caps["responses"] === true
60+
const text = (caps as Record<string, unknown>)["text"] === true
61+
if (chat || responses || text) return true
62+
}
63+
const id = extractModelId(m)
64+
if (id) return !isExcludedById(id)
65+
return false
66+
}
67+
if (typeof m === "string") {
68+
return !isExcludedById(m)
69+
}
70+
return false
71+
}
72+
3073
async function fetchModelsFromApi(auth?: string): Promise<UnrealModelId[]> {
3174
const headers: Record<string, string> = auth
3275
? { Authorization: `Bearer ${auth}` }
@@ -42,30 +85,28 @@ async function fetchModelsFromApi(auth?: string): Promise<UnrealModelId[]> {
4285
}
4386
const data: unknown = await resp.json()
4487

45-
let ids: string[] = []
88+
let items: unknown[] = []
4689
if (
4790
data &&
4891
typeof data === "object" &&
4992
Array.isArray((data as Record<string, unknown>).data)
5093
) {
51-
ids = ((data as Record<string, unknown>).data as unknown[])
52-
.map((m) => extractModelId(m as ApiModelLike))
53-
.filter((v): v is string => Boolean(v))
94+
items = (data as Record<string, unknown>).data as unknown[]
5495
} else if (
5596
data &&
5697
typeof data === "object" &&
5798
Array.isArray((data as Record<string, unknown>).models)
5899
) {
59-
ids = ((data as Record<string, unknown>).models as unknown[])
60-
.map((m) => extractModelId(m as ApiModelLike))
61-
.filter((v): v is string => Boolean(v))
100+
items = (data as Record<string, unknown>).models as unknown[]
62101
} else if (Array.isArray(data)) {
63-
ids = (data as unknown[])
64-
.map((m) => extractModelId(m as ApiModelLike))
65-
.filter((v): v is string => Boolean(v))
102+
items = data as unknown[]
66103
}
67104

68-
// De-duplicate
105+
const ids = items
106+
.filter((m) => hasTextCapability(m as ApiModelLike))
107+
.map((m) => extractModelId(m as ApiModelLike))
108+
.filter((v): v is string => Boolean(v))
109+
69110
const unique = Array.from(new Set(ids)) as UnrealModelId[]
70111
return unique
71112
}
@@ -100,5 +141,5 @@ export function invalidateModelsCache(): void {
100141
_modelsCache = null
101142
}
102143

103-
export const DEFAULT_MODEL: UnrealModelId ="glm-4p5"
104-
export const CODING_MODEL: UnrealModelId = "qwen3-coder-480b-a35b-instruct"
144+
export const DEFAULT_MODEL: UnrealModelId = "gpt-4o-mini"
145+
export const CODING_MODEL: UnrealModelId = "gpt-4o-mini"

0 commit comments

Comments
 (0)